BUUCTF(10)

224 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

[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

总结博客:www.leavesongs.com/PENETRATION…

[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/…

 

skysec.top/2019/05/18/…

因为我们无法得到命令执行回显,但可以得到网页执行的时间。

简单思考一下,前端做出的响应,一定是在3种后端都执行完毕后才进行响应。那么整个响应时间就会由3种后端,响应速度最慢的一个决定。那么我们是否可以只关注其中一个后端,让他的响应时间变为立即响应 / 延时5s响应,那么整个前端的时间就会变成立即响应 / 延时5s响应,那么我们就能通过前端的响应时间,来判断其中某个后端的执行结果是否成功

测试发现

我可以通过sleep函数成功控制响应时间。随机我马上测试了一下,判断这是哪个后端产生的问题

后补