SQL注入(SQL Injection)从注入到拖库 —— 简单的手工注入实战指南精讲
基本SQL注入步骤:
- 识别目标:确定目标网站或应用程序存在潜在的SQL注入漏洞。
- 收集信息:通过查看页面源代码、URL参数和可能的错误信息等,搜集与注入有关的信息。
- 判断注入点:确定可以注入的位置,比如输入框、URL参数等。
- 检测是否存在注入漏洞:通过在注入点输入特定的SQL语句,观察返回结果是否异常或错误,来判断是否存在注入漏洞。
- 确定数据库类型:如果存在注入漏洞,需要确定目标数据库的类型,如MySQL、Oracle等。
- 获取数据库版本:通过注入语句,尝试获取目标数据库的版本信息,便于后续选择合适的注入技术。
- 了解数据库结构:通过注入语句查询数据库结构,获取表名、列名等信息,以便后续查询数据。
- 执行恶意操作:根据之前的信息和目标需求,构造恶意的注入语句,执行数据库操作,如查询、插入、更新、删除等。
- 获取数据:根据注入语句,获取目标数据库中的敏感数据或其他有价值的信息。
- 覆盖痕迹:在完成操作后,隐藏或删除痕迹,以避免被发现。
注入操作基础步骤
这里以sqli-labs靶场Less-1为例
大家可以去BUUCTF上开一个靶机进行实验
1. 判断注入点
在GET参数、POST参数、Cookie、Referer、XFF、UA等地方尝试插入代码、符号或语句,尝试是否存在数据库参数读取行为,以及能否对其参数产生影响,如产生影响则说明存在注入点。
a. ?id=1’
ⅰ. 在 SQL 语句中,单引号用于表示字符串值。通过在注入点后面添加单引号 ',我们试图破坏正确的的 SQL 查询语句,以触发 SQL 语法错误来确认注入点。如果页面返回了类似于 “You have an error in your SQL syntax” 的错误信息,这意味着单引号导致了 SQL 语法错误,从而确认了存在注入点。
b. ?id=1’–+
ⅰ. 确定注入类型。根据原始sql语句,若没报错则确定为字符型注入。如果仍报错则可能为数字型。
ⅱ. 在 SQL 注释语法中,-- 被用作单行注释的开始符号,它会注释掉从它之后的所有内容。
ⅲ. 而 # 则用于一些特定的数据库,如 MySQL,其中它可以在注释行中使用。 使用#,发现执行的sql语句中没有#,原因是url中#号是用来指导浏览器动作的(例如锚点),对服务器端完全无用。所以,HTTP请求中不包括#,将#号改成url的编码%23就可以了。
ⅳ. 在URL中编码,+通常用作空格的替代符号。用来和后面的单引号分隔开,将后面的语句注释。如果–与后面的这个单引号直接连接在一起,无法形成有效的mysql语句。也可以使用–'来完成注入语句。
2. 探子回报
通过在可注入点进行尝试,确定数据库类型,当前账户权限级别等信息。
a. ?id=1’ order by 3 --+
ⅰ. ORDER BY是SQL语句中用于对查询结果进行排序的子句,它通常用于在SELECT语句中指定按照哪个或哪些列进行排序。
ⅱ. 使用ORDER BY子句来尝试确定查询结果中显示的列数。通过在注入点后添加order by 1、order by 2等数字递增,观察页面的变化来判断查询结果是否受到影响。此例order by 3不报错,order by 4报错,则可判断列数为3。本例为3。
ⅲ. 使用order by语句还可以判断数据库类型:根据返回结果判断数据库类型,如MySQL、SQL Server、Oracle等。
b. 其他操作
ⅰ. 使用error信息判断数据库版本:在可注入点尝试使用error信息函数,如mysql_error()、odbc_error()等,根据返回的错误信息判断数据库版本号。
ⅱ. 使用时间延迟函数判断操作系统类型:在可注入点尝试使用时间延迟函数,如benchmark()、sleep()等,根据返回的时间差值判断操作系统类型。
3. 判断字段数(列数)和回显点
通过查询相关内容,判断库名、表名和列名,查询具体信息。
a. ?id=-1’ union select 1,2,3–+
ⅰ. id=-1 是为了避免回显信息与现有的有效 id 值发生冲突,以确保我们的注入语句能够准确地执行。通过选择一个无效的 id 值(-1 是常见的选择),我们可以成功地绕过原始查询中基于有效 id 值的限制条件,并通过 union select 语句进行注入回显操作。
ⅱ. 使用union select语句判断表名和列数:在可注入点尝试使用union select语句,利用已知列数爆出显示位 ,以便获取更多信息。如果页面中出现1,2,3任一字样,则为爆出了的显示位,可在后续代码中替换为信息获取函数。本例为2,3。
ⅲ. 猜想:能够回显的原因是 因为select选择的结果集(1,2,3)与当前数据表的列数相同,当id值为无效的 id 值-1时,结果集就会代替设定好需要显示的原数据显示出来。
b. ?id=-1’ union select 1,version(),database() --+
ⅰ. 根据上一步爆出的回显位置,改为信息函数查看信息。相关获取信息函数如下:
■ version():数据库版本-看是否符合information_schema查询
■ user():数据库用户-看是否符合ROOT型注入攻击
■ @@version_compile_os:当前操作系统-看是否支持大小写或文件路径选择
■ database():数据库名字-为后期猜解指定数据库下的表,列做准备。本例的表名爆出为security。
c. ?id=-1’ union select 1,group_concat(table_name) ,3 from information_schema.tables where table_schema=‘security’–+
ⅰ. MySQL 5.0以上版本自带数据库information_schema
■ information_schema:存储数据库下的数据库名及表名,列名信息的数据库
■ information_schema.tables:记录表名信息的表
■ information_schema.columns:记录列名信息表
ⅱ. 使用information_schema表查询数据库信息:在可注入点尝试使用information_schema表查询数据库信息,如数据库名、用户名、密码等。 本例中group_concat(table_name) from information_schema.tables where table_schema=‘security’;,意为从information_schema.tables表中筛选出所有表名(table_name)属于名为’security’的数据库模式(table_schema),然后将这些表名连接成一个字符串(group_concat())。
d. ?id=-1’ union select 1,group_concat(“\n”,column_name) ,3 from information_schema.columns where table_name=‘users’–+
·
ⅰ. 爆字段名,我们通过sql语句查询知道当前数据库有四个表,根据表名知道可能用户的账户和密码是在users表中,所以我们可以尝试得到并查看该表下的字段名以及内容。
ⅱ. 这个查询语句的作用是从information_schema.columns表中筛选出表名为’users’的所有列名,并将它们连接成一个字符串。table_name字段表示所有字段对应的表名,所有表中都有。
ⅲ. MySQL 库名、表名和列名的获取思路就是:
■ 获取当前数据库名:database() => ‘security’
■ 获取当前数据库的所有表名:select group_concat(table_name) from information_schema.tables where table_schema=‘security’ => ‘emails,users,…’
■ 获取所选表的所有列名:select group_concat(culumn_name) from information_schema.columns where table_name=‘users’ => ‘username ,password,…’
e. ?id=-1’ union select 1,group_concat(“
”,username ," ", password),3 from users–+
ⅰ. 爆出敏感信息,这里使用select from语句,结合group_concat()连接上一步得知的字段。我加了空格和换行隔一下账户和密码。
-
提权分析:根据已知的用户级别和数据库信息,决定是否需要进一步提权以获取更高的操作权限。
-
利用漏洞进行攻击:构造特定的SQL语句,通过网页输入的方式进行注入,执行非授权操作,如查询、修改或删除数据等。
📌需要注意的是,SQL注入是一种非法的攻击行为,务必在合法授权和法律规定的范围内进行安全测试和演练。