代码审计-企业级Web代码安全架构
😍

代码审计-企业级Web代码安全架构

Published
March 28, 2023
Category
防御与加固
Subcategory
常见威胁与攻击类型
Tags
代码审计
Author
HZH
Notes

代码审计前的准备

常量
含义
PHP_INI_USER
该配置选项可在用户的PHP脚本或者Windows注册表中设置
PHP_INI_PERDIR
该配置选项可以在php.ini .htaccess或者httpd.conf中设置
PHP_INI_SYSTEM
该配置选项可以在php.ini或者httpd.conf中设置
PHP_INI_ALL
该配置选项可以在任何地方设置
php.ini only
该配置选项可仅可以在php.ini 中设置
  • register_globals(全局变量注册开关)
该选项在设置为on的情况下,会直接把用户GET、POST等方式提交上来的参数注册为全局变量并初始化值为参数对应的值,使得提交参数可以直接在脚本中使用。
  • allow_url_include(是否允许包含远程文件)
在该配置为on的情况下,它可以直接包含远程文件,当存在include($var)且$var可控的情况下,可以直接控制$var变量来执行PHP代码。与之类似的配置还有allow_url_fopen,配置是否允许打开远程文件。
  • magic_quotes_gpc(魔术引号自动过滤)
magic_quotes_gpc在安全方面做了很大的贡献,只要它被开启,在不存在编码或者其他特殊绕过的情况下,可以使得很多漏洞无法被利用。当该选项设置为on时,会自动在GET、POST、COOKIE变量中的单引号、双引号、反斜杠以及空字符的前面加上反斜杠。但是在PHP5中magic_quotes_gpc并不会过滤$_SERVER变量,导致很多类似client-ip、refer一类的漏洞能够被利用。在PHP5.3之后不推荐,PHP5.4之后干脆取消。
  • magic_quotes_runtime(魔术引号自动过滤)
magic_quotes_runtim也是自动在单引号、双引号、反斜杠以及空字符的前面加上反斜杠。magic_quotes_runtime只对从数据库或者文件中获取的数据进行过滤。
  • magic_quotes_sybase(魔术引号自动过滤)
magic_quotes_sybase是对GET、POST、Cookie进行处理,并且覆盖magic_quotes_gpc,并且仅仅是转义了空字符和把单引号变成双引号。
  • safe_mode(安全模式)
安全模式是PHP内嵌的一种安全机制。这个配置会出现下面限制:
  • 所有文件操作函数(如unlink()、file()和include())等都会受到限制
  • 通过函数popen()、system()以及exec()等函数执行命令或者程序会提示错误
  • open_basedir (PHP可访问目录)
open_basedir指令用来限制PHP只能访问哪些目录,通常我们只需要设置Web文件目录既可,如果需要加载外部脚本,也需要把脚本所在目录路径加入到open_basedir指令中,多个目录以分号(;)分割
  • disable_function(禁用函数)
在正式的生产环境中,为了更安全地运行PHP,也可以使用disable_function指令来禁止一些敏感函数的使用
  • dispaly_errorserror_reporting错误显示
dispaly_errors表示是否显示PHP脚本内部错误的选项,一般调试PHP时打开,但在生产环境中一般关闭。error_reporting用来配置错误显示的级别

漏洞挖掘与防范

SQL注入

原因
开发者在编写操作数据库代码时,直接将外部可控的参数拼接到SQL语句中,没有经过任何过滤就直接放入数据库引擎执行
注入类型
  • 普通注入:使用Union等联合查询得方式进行注入
  • 编码注入:程序在进行一些操作之前经常会进行一些编码处理,而做编码处理的函数也是存在问题的,通过输入转码函数不兼容的特殊字符,可以导致输出的字符编码成有害数据,在SQL注入里,最常见的编码注入是MySQL宽字节以及urldecode/rawurldecode函数导致的
    • URL编码: 攻击者可以使用URL编码对特殊字符进行编码,使得过滤器无法识别这些字符。例如,攻击者可以将单引号(')替换为其URL编码形式(%27)
      • ?id=1%27+UNION+SELECT+1,2,3+FROM+users--+
    • Unicode编码: 攻击者可以尝试使用Unicode编码来绕过安全过滤。例如,攻击者可以将单引号(')替换为Unicode编码(\u0027)
      • ?id=1\u0027+UNION+SELECT+1,2,3+FROM+users--+
    • 字符转义: 攻击者可以尝试通过在特殊字符前添加反斜杠(\)来转义这些字符,绕过安全过滤。例如,攻击者可以将单引号(')转义为('):
      • ?id=1\\'+UNION+SELECT+1,2,3+FROM+users--+
    • 使用数据库内置函数: 攻击者可以尝试利用数据库的内置函数,如MySQL的CHAR()函数,将特殊字符转换为其ASCII码,从而绕过安全过滤。例如,攻击者可以将单引号(')替换为(CHAR(39))
      • ?id=1+CONCAT(CHAR(39),+UNION+SELECT+1,2,3+FROM+users,CHAR(39))--+
    • 宽字节注入:set character_set_client=gbk,当存在宽字节注入漏洞时,注入参数中带入%df%27,既可把程序中过滤的\(%5c)吃掉。
    • 二次urldecode注入:通过使用addslashes()mysql_real_escape_string()mysql_escape_string() 函数或者开启GPC的方式来防止注入,也就是给单引号、双引号、反斜杠和NULL加上反斜杠转义。如果某处使用了urldecoderawurldecode 函数,则会导致二次解码生成单引号而引发注入。
      • 首先,urldecoderawurldecode函数是用来解码URL编码的字符串。它们将URL编码的特殊字符还原为原始字符。例如,%20会被解码为一个空格,%3C会被解码为<等。
      • 现在让我们理解为什么二次解码可能会导致SQL注入。假设攻击者尝试提交一个经过URL编码的SQL注入字符串,如%27%20or%201=1%20--%20。这个字符串在解码后变为' or 1=1 -- 。如果应用程序仅对输入进行了一次解码,那么解码后的字符串可能会被插入到SQL查询中,导致SQL注入。
      • 然而,如果应用程序在解码输入后,又使用urldecoderawurldecode对已解码的输入进行了二次解码,可能会发生以下情况:攻击者提交了一个双重URL编码的字符串,如%2527%2520or%25201%253D1%2520--%2520。这个字符串在第一次解码后变为%27%20or%201=1%20--%20,第二次解码后变为' or 1=1 -- 。这样,即使应用程序在第一次解码后对输入进行了过滤或验证,攻击者仍然可以通过二次解码绕过这些措施,从而导致SQL注入。
      • 因此,为了避免这种安全风险,建议仅对输入进行一次解码,并确保在执行SQL查询之前对输入进行严格的验证和过滤。避免在不必要的情况下使用urldecoderawurldecode对已解码的输入进行二次解码。
  • 漏洞防范:
    • gpc/runtime魔术引号
    • 过滤函数和类
      • addslashes函数
        • mysql_quotes_gpcmysql_quotes_runtime起到的效果相同,对单引号、双引号、反斜杠以及空字符NLL进行过滤
      • mysql_[real]escape_string函数
      • intval和floatval
        • 将输入的字符串转变为int型
      • PDO prepare预编译

XSS

  • 特殊字符HTML实体转码。HTML编码是一种将特殊字符转换为其HTML实体表示形式的方法,从而防止浏览器将这些字符解释为脚本代码。通过对用户输入进行HTML编码,您可以确保包含在页面中的文本不会被误解为HTML标签或JavaScript代码,从而避免XSS攻击。
  • 为了保证数据原始性,最好的过滤方式是在输出和二次调用的时候进行如HTML实体一类的转码,防止脚本注入的问题
挖掘XSS漏洞的关键在于寻找没有被过滤的参数,且这些参数传入到输出函数

CSRF漏洞

劫持其他用户去进行一些请求
漏洞防范
  • Token/Referer验证
    • 在页面或者cookie里面加入一个不可预测的字符串,服务器在接受操作请求的时候只要验证这个字符串是不是上次访问留下的即可判断是不是可信请求,因为如果没有访问上一个页面,是无法得到这个token的,除非结合XSS漏洞或者有其他手段能获得通信数据。当攻击者构造一个恶意请求时,他们需要知道有效的CSRF令牌才能成功。但是,攻击者无法直接访问到受害者的Cookie,因此他们也无法获得有效的CSRF令牌。攻击者可以尝试猜测令牌,但由于令牌具有不可预测性和足够的随机性,这种猜测几乎是不可能的。
    • 假设表单的HTML代码如下所示:
      • <form action="/submit-comment" method="POST"> <input type="hidden" name="csrf_token" value="1234567890"> <textarea name="comment"></textarea> <button type="submit">提交评论</button> </form>
        注意,表单中有一个隐藏字段csrf_token,其值设置为用户会话中的CSRF令牌。当用户提交表单时,浏览器会将表单数据(包括csrf_token字段)发送给服务器。
        服务器接收到请求后,会检查请求中的CSRF令牌是否与服务器为该用户存储的令牌匹配。如果令牌匹配,服务器将执行评论提交操作。如果令牌不匹配,服务器将拒绝请求。
        现在,假设攻击者试图发起一个CSRF攻击,他们需要构建一个恶意请求,使受害者在不知情的情况下提交评论。攻击者可能会创建一个如下的恶意HTML页面:
        <form action="https://example.com/submit-comment" method="POST" id="maliciousForm"> <input type="hidden" name="comment" value="这是一个恶意评论"> </form> <script> document.getElementById("maliciousForm").submit(); </script>
        当受害者访问这个恶意页面时,表单会自动提交,尝试向example.com 发起评论提交请求。但是,这个恶意表单没有包含有效的CSRF令牌。因此,当服务器收到这个请求时,它会发现CSRF令牌不匹配,从而拒绝该请求。
  • 增加验证码

文件操作漏洞

文件包含漏洞

  • 关键函数:include()include_once()require()require_once()include()include_onece()在包含文件时即使遇上错误,下面的代码依然会继续执行,而require()require_once()则会直接报错退出程序。
  • 本地文件包含(Local file include,LFI)
  • 远程文件包含(remoete file include、RFI)
  • 文件包含截断
    • %00截断
      • 受限于GPC和addslashes等函数的过滤,并且在PHP5.3之后也不能过通过这种方式截断
    • 长度截断
      • %00截断是一种攻击技术,其原理是利用某些编程语言(如C和C++)在处理字符串时对空字节(NULL字节,即\0%00)的特殊处理方式。在这些编程语言中,空字节被视为字符串的终止符。当程序遇到空字节时,它会停止处理字符串。因此,攻击者可以利用这一特性绕过文件上传的安全限制。
      • 利用多个英文句号(.)和反斜杠(/)来截断,这种方法不受GPC限制,不过同样在PHP5.3版本之后被修复了

文件读取(下载)漏洞

  • 文件读取函数列表:file_get_contents()highlight_file()fopen()readfile()fread()fgetss()fgets()parse_ini_file()show_source()file(),甚至包括文件包含函数includes等,可以利用PHP输入输出流php://filter/来读取文件
  • php://filter是一种PHP特有的输入输出流封装器,用于对数据流进行过滤和处理。它允许在数据流读取或写入过程中对数据进行处理,例如进行编码、解码、压缩、加密等操作。在这里,php://filter可以被用于利用文件读取函数来绕过某些限制,以实现未授权文件读取。
    • 例如,当一个Web应用程序允许用户查看某个文件的内容,但对文件类型进行了限制时,攻击者可以通过使用php://filter来绕过这些限制。假设应用程序只允许用户查看.txt文件,但攻击者希望查看一个PHP文件(例如config.php)的内容,可以尝试使用以下方法:
    • http://example.com/view_file.php?file=php://filter/convert.base64-encode/resource=config.php
    • 在这个例子中,convert.base64-encode是一个过滤器,用于对文件内容进行Base64编码。resource=config.php表示我们希望对config.php文件应用此过滤器。这样,当应用程序使用file_get_contents()或其他文件读取函数读取文件时,它实际上会读取config.php文件的Base64编码内容。
    • 攻击者可以从Web应用程序的响应中提取Base64编码的内容,然后进行解码,从而查看原始的PHP文件内容。

文件上传漏洞

  • 应用程序都是代码写的,代码都是写在文件里面执行的,如果能把文件上传到管理员或者应用程序不想让你上传的目录,那就是存在文件上传漏洞。注意这里并不是说一定是上传一个WebServer可以解析的代码文件到可以解析的目录,漏洞的定义是做攻击目标不想让你做的事情,而你又发现可以做到。
  • 上传函数:move_uploaded_file()
  • 未过滤或者本地过滤
  • 黑名单扩展名过滤
    • 限制的扩展名不够全
    • 验证扩展名的方式存在问题可以直接绕过
  • 文件头、content-type验证绕过
    • 文件头,当使用getimagesize()的时候来判断文件时,在文件头加上”GIF89a”的字符,这样就会返回一个图片的尺寸数据
    • content-type在http request的请求头里,所以这个值是可以由请求者自定义修改的
  • 文件上传漏洞防范:
    • 白名单方式过滤文件拓展名,使用in_array或者三等于(===)来对比扩展名
    • 保存上传的文件时重命名文件,文件名命名规则采用时间戳的凭借随机数的MD5值方式“md5(time()+ rand(1,10000))

文件删除漏洞

  • 它的漏洞原理跟文件读取漏洞差不多,不过是利用的函数不一样而已,一般也是因为删除的文件名可以用./跳转,或者没有限制当前用户只能删除他该有权限删除的文件。常出现这个漏洞的函数是unlink()
  • 文件操作漏洞防范
    • 通用文件操作防御
      • 由越权操作引起可以操作未授权操作的文件
      • 要操作更多文件需要跳转目录
      • 大多都是直接在请求中传入文件名
    • 文件上传漏洞防范

代码执行漏洞

  • 该漏洞主要由eval()assert()preg_replace()call_user_func()call_user_func_array()array_map()等函数的参数过滤不严格导致,另外还有PHP的动态函数($a($b))也是目前出现比较多的。
  • PHP中的动态函数调用是指在运行时通过变量名来调用一个函数。这可以让代码更灵活和模块化,但同时也可能导致安全问题,尤其是当用户输入用于指定要调用的函数时。在这种情况下,攻击者可能能够通过提交恶意输入来触发意外的函数调用,从而执行任意PHP代码。
  • 以下是一个简单的示例,演示了动态函数调用的使用:
$user_function = $_GET['function_name']; $argument = $_GET['argument']; $result = $user_function($argument); echo $result;
  • 在这个示例中,应用程序从GET参数function_name获取函数名,并将其存储在变量$user_function中。接下来,从GET参数argument获取参数值,并将其存储在变量$argument中。然后,应用程序使用动态函数调用将$user_function指定的函数应用于$argument,并输出结果。
由于这里没有对用户输入进行任何验证,攻击者可以通过提交恶意的GET参数来利用此漏洞。例如,攻击者可以通过提交function_name=system&argument=ls来尝试执行system('ls'),从而在服务器上执行任意命令。

命令执行漏洞

  • 代码执行漏洞指的是可以执行PHP脚本代码,而命令执行漏洞指的是可以执行系统或者应用指令的漏洞,PHP的命令执行漏洞主要是基于一些函数的参数过滤不严格导致,可以执行命令的函数有system()exec()shell_exec()passthru()pcntl_exec()popen()proc_open(),一共七个函数
  • 命令防注入函数
    • PHP在SQL防注入上有addslashed()mysql_[real_]escape_string()等函数过滤SQL语句,在命令上也同样有防注入函数,一共有两个escapeshellcmd()escapeshellarg()escapeshellcmd()是过滤的整条命令,escapeshellarg()是过滤参数

变量覆盖漏洞

  • 变量覆盖指的是可以用我们自定义的参数值替换程序原有的变量值,变量覆盖漏洞通常需要结合程序的其他功能来实现完整攻击,比如原本一个文本上传页面,限制的文件拓展名白名单列表写在配置文件中的变量中,但是在上传的过程中有一个变量覆盖漏洞可以将任意拓展名覆盖掉原来的白名单列表,那么我们就可以覆盖进一个PHP的拓展名,从而实现一个PHP的shell
  • 引发的函数有:extract()函数和parse_str()import_request_variables()

逻辑处理漏洞

  • 广义上来说,大多数的漏洞都是由于程序的逻辑失误导致的,都可以叫做逻辑漏洞,但我们这里说的逻辑漏洞并没有那么大的范围,这里指程序在业务逻辑上面的漏洞,业务逻辑漏洞也是一个不小的范围,在不同的业务场景有不同的漏洞出现,目前逻辑漏洞是各大企业存在最多的漏洞之一,因为逻辑漏洞在挖掘和利用时都需要进行一些逻辑判断,机器代码很难模拟这块的逻辑处理,所以无法用机器批量化扫描检测,检测的少了,现存的漏洞自然就多了
  • 值得关注的点是程序是否可以重复安装、修改密码处是否可以越权修改其他用户密码、找回密码验证码是否可暴力破解以及修改其他用户密码、cookie是否可以预测或者说cookie验证是否可以绕过
  • in_arrayis_numeric
  • 双等于和三等于,双等于在进行比较时会进行类型转换,而三等于则不会

会话认证漏洞

  • 会话认证是一个非常大的话题,涉及各种认证协议和框架,如cookie、session、sso、oauth、openid等,出现问题比较多的在cookie上面,cookie是Web服务器返回给客户端的一段常用来标识用户身份或者认证情况的字符串,保存在客户端,浏览器在下次请求时会自动带上这个标识,由于这个标识字符串可以被用户修改,所以存在安全风险,一般这块的认证安全问题都出在服务端直接取用cookie的数据而没有校验,其次是cookie加密数据存在可预测的情况。另外是session是保存在服务器端的信息,如果没有代码操作,客户端不能直接修改session,相对比较安全。
  • 漏洞防范:所有用户输入的值是不完全可信的,所以在防御认证漏洞之前,我们应该先了解认证的业务逻辑,严格限制输入的异常字符以及避免使用客户端提交上来的内容直接进行操作。

二次漏洞审计

  • 二次漏洞有点像存储型XSS的味道,就算payload插进去了,能不能利用还得看页面输出有没有过滤
 

代码审计技巧

钻GPC等转义的空子

  • GPC会自动把我们提交的单引号等敏感字符给转义掉,这样我们的攻击代码就没法执行了,GPC是PHP天生自带的功能,所以是我们最大的天敌。不过GPC并不是把所有变量都进行了过滤,反而人们容易忽视而又用得多的$_SERVER变量没有被GPC过滤,包括编码转换的过程中。
  • 不受GPC保护的$_SERVER变量,在PHP5之后用$_SERVER取到的header字段不受GPC影响,所以当GPC开启的适合,它里面的特殊字符如单引号也不会被转义掉。而在header注入里面最常见的是user-agentreferer以及client-ip/x-forward-for,因为大多的Web应用都会记录访问者的IP以及refer等信息。同样的$_FILES变量一样不受GPC保护。
  • 编码转换问题,GBK到UTF8

神奇的字符串

  • 字符处理函数报错信息泄露
    • 页面的报错信息通常能泄露文件绝对路径、代码、变量以及函数等信息,页面报错有很多情况,比如参数少了或者多了、参数类型不对、数组下标越界、页面超时等,不过并不是所有的情况下页面都会出现错误信息,要显示错误信息需要打开在PHP设置文件php.ini中设置display_errors=on或者在代码中加入error_reporting()函数,error_reporting()函数有几个选项来配置显示错误的等级
  • 字符串截断
    • %00空字符截断【既可以绕过文件扩展名的检查,又能把脚本文件写入到服务器中】
    • iconv函数字符编码转换截断

php://输入输出流

  • PHP提供了php://的协议允许访问PHP的输入输出流、标准输入输出和错误描述符,内存中、磁盘备份的临时文件流以及可以操作其他读取写入文件资源的过滤器。主要提供如下访问方式来使用这些封装器
    • php://stdin php://stdout php://stderr php://input php://output php://fd php://memory php://temp php://filter
      使用最多的是php://inputphp://output以及php://filter。
      其中php://input是可以访问请求的原始数据的只读流。即可以读取到POST上没有经过解析的原始数据。
      php://output 是一个只写的数据流,跟php://input相反,php://input是读取PSOT提交上来的数据,而php://ouput是将流数据输出。
      php://filter是一个文件操作的协议,可以对磁盘中的文件进行读写操作,效果类似于readfile()file()file_get_contents(),它有多个参数可以进行相应的操作
      resource=<要过滤的数据流>
      该参数是必须的,它指定了你要筛选过滤的数据流
      read=<该链的筛选数据流>
      该参数可选,可以设定一个或者多个过滤器名称,以管道符(|)分隔
      <;两个链的筛选列表>
      任何没有以read=或write=作前缀的筛选器列表会视情况应用于读或写链
      例子:
      <?php include($_GET['f']) ?>
      /1.php?f=php://filter/convert.base64-encode/resource=1.php

fuzz漏洞发现

  • fuzz指的是对特定目标的模糊测试,这里需要注意的是,针对特定目标甚至说是特定请求,它不同于漏洞扫描器进行批量漏洞扫描,不过它的初衷都是以发现bug为目的。

不严谨的正则表达式

  • 没有使用^和$限定匹配开始位置
  • 特殊字符未转义

十余种MySQL报错注入

  • floor()
  • extractvalue()
  • updatexml()
  • GeometryCollection()
  • polygon()
  • multipoint()
  • multiinestring()
  • multipolygon()
  • linestring()
  • exp()

Windows FindFirstFile利用

  • 当上传的webshell文件被重命名,在windows环境下使用到了FindFirstFile这一个winapi函数,该函数到一个文件夹(包括子文件夹)去搜索指定文件。
  • 利用方法很简答,只要将文件名不可用的部分之后的字符用“<”或者”>”代替即可,不过需要注意的是,只使用一个”<”或者”>”则只能代表一个字符,如果文件名是12345或者更长,这时候请求“1<”或者”1>”都是访问不到文件的,需要通过“1<<”才能访问到,代表继续往下搜索。这样我们还可以通过这个方式来爆破目录文件了。

PHP可变变量

  • 在PHP语言中,单引号和双引号是有区别的,单引号代表纯字符串,双引号代表则是会解析中间的变量

PHP安全编程规范

参数的安全过滤

  • SQL注入过滤函数
    • addslashes()mysql_real_escape_string()mysql_escape_string(),它们的作用都是给字符串加反斜杠来进行转义
  • XSS过滤函数
    • htmlspecialchars()strip_tag()
      • htmlspecialchars()是将特殊字符转换成HTML实体编码
      • strip_tag()是用来去掉HTML以及PHP标记
  • 命令执行过滤函数
    • escapeshellcmd()escapeshellarg()
      • escapeshellarg()函数过滤方式比较简单,给所有参数加上一对双引号,强制为字符串
      • escapeshellcmd()则是在特殊字符前加上了反斜杠

安全加密算法

  • 对称加密
    • 3DSE加密
    • AES加密
  • 非对称加密
    • RSA加密
  • 单向加密【不可逆算法】
    • MD5/sha1加密

业务功能安全设计

  • 验证码
    • 验证码绕过
      • 不刷新直接绕过
        • Web页面登录等操作的验证码能够多次使用的原因是后端程序在接收一次请求后,并没有主动刷新验证码,部分比较大的业务使用了负载均衡,验证码跟Session绑定在一起,为了能够保证验证码能够正常使用,所以会把验证码明文或者加密后放在Cookie或者POST数据包里面,所以每次只要用同一个数据包里面的两个验证码对上了即可绕过。
      • 暴力破解
      • 机器识别
      • 打码平台
    • 防范:
      • 设置验证码错误次数
      • 不要把验证码放到HTML页面或者Cookie中
      • 验证码要设置只能请求一次,请求一次之后不论错误与否强制刷新
  • 用户登录
    • 撞库漏洞
      • 用户名和密码错误次数都没有限制
      • 单时间内用户的密码错误次数限制
      • 单时间段内IP登录错误次数限制
    • API登录
    • 用户注册
    • 密码找回