本文已参与「新人创作礼」活动,一起开启掘金创作之路。
[Windows]LFI2019
是用不含数字和字母的webshell。
- 思路一:两个非字母、数字的字符进行异或得到结果
- 思路二:利用位运算里的取反,利用UTF-8的某个汉字
思路三:借助PHP的一个小技巧,也就是说,'a'++ => 'b','b'++ => 'c'... 所以,我们只要能拿到一个变量,其值为a,通过自增操作即可获得a-z中所有字符。
那么,如何拿到一个值为字符串'a'的变量呢?
巧了,数组(Array)的第一个字母就是大写A,而且第4个字母是小写a。也就是说,我们可以同时拿到小写和大写A,等于我们就可以拿到a-z和A-Z的所有字母。
- 在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为
Array
[RCTF2019]calcalcalc
题目给出了源码,我们先进行审计
python
from flask import Flask, request
import bson
import json
import datetime
app = Flask(__name__)
@app.route("/", methods=["POST"])
def calculate():
data = request.get_data()
expr = bson.BSON(data).decode()
if 'exec' in dir(__builtins__):
del __builtins__.exec
return bson.BSON.encode({
"ret": str(eval(str(expr['expression'])))
})
if __name__ == "__main__":
app.run("0.0.0.0", 80)
php
<?php
ob_start();
$input = file_get_contents('php://input');
$options = MongoDB\BSON\toPHP($input);
$ret = eval('return ' . (string) $options->expression . ';');
echo MongoDB\BSON\fromPHP(['ret' => (string) $ret]);
加的限制
disable_functions = set_time_limit,ini_set,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld,mail,putenv,error_log
max_execution_time = 1
node.js
const express = require('express')
const bson = require('bson')
const bodyParser = require('body-parser')
const cluster = require('cluster')
const app = express()
if (cluster.isMaster) {
app.use(bodyParser.raw({ inflate: true, limit: '10kb', type: '*/*' }))
app.post('/', (req, res) => {
const body = req.body
const data = bson.deserialize(Buffer.from(body))
const worker = cluster.fork()
worker.send(data.expression.toString())
worker.on('message', (ret) => {
res.write(bson.serialize({ ret: ret.toString() }))
res.end()
})
setTimeout(() => {
if (!worker.isDead()) {
try {
worker.kill()
} catch (e) {
}
}
if (!res._headerSent) {
res.write(bson.serialize({ ret: 'timeout' }))
res.end()
}
}, 1000)
})
app.listen(80, () => {
console.log('Server created')
})
} else {
(function () {
const Module = require('module')
const _require = Module.prototype.require
Module.prototype.require = (arg) => {
if (['os', 'child_process', 'vm', 'cluster'].includes(arg)) {
return null
}
return _require.call(_require, arg)
}
})()
process.on('message', msg => {
const ret = eval(msg)
process.send(ret)
process.exit(0)
})
}
属实复杂,我们慢慢看,总体来说使用了三种后端:nodejs、php、python
原理很清晰,我们input的参数,会分别进入3种后端进行执行,如果3种后端最后的返回值不同,那么则认定为无效,会做一些处理。如果返回值一致,认定为安全,则将执行结果返回
我们先测试一下直接输入字符会发现提示非法信息
而正常的运算则会出结果
| That's classified information. - Asahina Mikuru |
|---|
限制
del __builtins__.exec
直接eval参数
return bson.BSON.encode({
"ret": str(eval(str(expr['expression'])))
})
还限制了响应时间
php也是直接命令执行
最后的nodejs由于看不太懂,从wp里找了一段
做出了时间限制和一些过滤,并且也会直接执行参数
总结下来,都直接eval参数,设置了时间限制,过滤了一些函数
至于怎么利用,属实没太想到
wp写的是
| skysec.top/2017/12/29/… |
|---|
因为我们无法得到命令执行回显,但可以得到网页执行的时间。
简单思考一下,前端做出的响应,一定是在3种后端都执行完毕后才进行响应。那么整个响应时间就会由3种后端,响应速度最慢的一个决定。那么我们是否可以只关注其中一个后端,让他的响应时间变为立即响应 / 延时5s响应,那么整个前端的时间就会变成立即响应 / 延时5s响应,那么我们就能通过前端的响应时间,来判断其中某个后端的执行结果是否成功
测试发现
我可以通过sleep函数成功控制响应时间。随机我马上测试了一下,判断这是哪个后端产生的问题
后补