Skip to content

Latest commit

 

History

History
522 lines (262 loc) · 12.8 KB

漏洞修复指南.md

File metadata and controls

522 lines (262 loc) · 12.8 KB

1、 跨站脚本XSS

1.1反射xss

1.1.1 输出在html 标签内或者普通属性(非href、非事件类)

调用如下的HtmlEncode 函数进行编码,php 示例代码,其他语言可以参照实现。

  
function HtmlEncode($str)
  
{
  
    $str = str_replace("&","&", $str);
  
    $str = str_replace(">",">", $str);
  
    $str = str_replace("<","&lt;", $str);
  
    $str = str_replace("\"","&quot;", $str);
  
    $str = str_replace("'","&#39;",  $str);
  
    $str = preg_replace("/\s+/","&#32;", $str);
  
    return $str;
  
}
  

注:为了预防业务将参数输出至属性值时未使用引号包裹的情况,这里自己实现的HtmlEncode 也将空格编码。

1.1.2 输出在html 标签的 href 属性内

一般来说,如果变量是完整的url,应该先检查下是否以^http开头,且目的域名是允许的域名列表内,以保证不会出现伪协议类的XSS攻击。接着使用 HtmlEncode编码下;或者因为此处是地址栏,可以用 php标准函数urlencode 编码变量。

1.1.3 输出在html 标签的事件属性内(如onXX 事件)

使用 jsencode(\uUUUU) 编码,php 示例代码,其他语言可以参照实现。

  
function jsencode($str)
  
{
  
    $arr = array();
  
    $str_len = strlen($str);
  
    $need_encode = "<>\"&#`()[]';";
  
    for($i = 0; $i < $str_len; $i++)
  
    {
  
        if (strpos($need_encode, $str[$i]))
  
        {
  
            $arr[$i] = "\\u00" . bin2hex($str[$i]);
  
        }
  
        else
  
        {
  
            $arr[$i] = $str[$i];
  
        }
  
    }
  
    return join("", $arr);
  
 }
  

1.1.4 在富文本框内进行过滤

参考 这里 进行标签和属性的白名单过滤。

1.2 存储XSS

同反射XSS,在输出变量时参照如上情形描述。

1.3 DOMXSS

在"$var" 输出到<script> 时,应该执行一次 jsencode(\uUUUU);其次,在document.write 输出到页面 html 页面时,要分具体情况对待:如果是输出到事件或者脚本,则要再做一次 jsEncode;如果是输出到 html 内容或者属性,则要再做一次 HtmlEncode。

如下情况属于后者,即 HtmlEncode (jsencode($var))

  
<script>
  
var x = "$var";
  
document.write("<a href='"+x+"' >test</a>");
  
</script>
  

Javascript 函数代码示例如下:

  
function jsencode(str) {
  
    var arr = [];
  
    var need_encode = "<>\"&#`()[]';";
  
    for (var i = 0; i < str.length; i++) {
  
 
  
        if (need_encode.indexOf(str[i]) != -1)
  
        {
  
            arr[i] = ("\\u00" + str.charCodeAt(i).toString(16)).slice(-6);
  
        }
  
        else
  
        {
  
            arr[i] = str[i];
  
        }
  
    }
  
    return arr.join("");
  
}
  

  
function HtmlEncode(sStr)
  
{
  
	sStr = sStr.replace(/&/g,"&amp;");
  
	sStr = sStr.replace(/>/g,"&gt;");
  
	sStr = sStr.replace(/</g,"&lt;");
  
	sStr = sStr.replace(/"/g,"&quot;");
  
	sStr = sStr.replace(/'/g,"&#39;");
  
	sStr = sStr.replace(/\s+/g,"&#32;");
  
	return sStr;
  
}
  

在使用location.href / location.replace() / location.assign() 等函数实现前端跳转时不止要考虑url 任意跳转的问题,如1.1.2 所说也要避免伪协议引起的xss攻击。

1.4 flash xss

建议不再使用的swf请下线,其他有漏洞的swf请更新至官方最新版,或者用其他功能类似swf 替代实现。

2、 跨站请求伪造CSRF

2.1 校验refer

Refer 校验需要严格的正则,比如 /^http://www.qq.com/index.html$/,否则可能被绕过。但是某些情况下发出的情况Refer 为空甚至以前曾经出现过flash请求可以伪造Refer,故更敏感的一些操作建议使用验证token方式。

2.2 验证token

即在页面埋下隐藏的动态token值,后端逻辑对这个token 值进行验证,如果不正确则直接退出。php 代码示例如下:

  
<?php
  

  
session_start();
  
if (isset($_SESSION['token']) && isset($_POST['username'])) {
  
    if (empty($_POST['token']) || $_POST['token'] != $_SESSION['token']) {
  
        header('Location: http://localhost/error.php?token=' . $_SESSION['token']);
  
        exit(-1);
  
    }
  
}
  

  
$token   = md5(time());
  
$_SESSION['token'] = $token;
  
?>
  

  
<html>
  
<head>
  
    <title>test with csrf token</title>
  
</head>
  
<body>
  
<form method="post" action="/csrf.php" id="form">
  
    <div>
  
        <input name="username" type="text" id="username" style="width:270px;"/>
  
        <input type="submit" name="btnSearch" value="Search" id="btnSearch" />
  
        <input type="hidden" name="token" id="token" value="<?php echo $token;?>" />
  
    </div>
  
</form>
  

  
<br/>
  
</body>
  
</html>
  

2.3 验证码

在特别关键的操作建议启用,其他操作建议预埋验证码,紧急时刻(如出现CSRF蠕虫)可以临时启用。

3、SQL 注入

3.1 首选绑定参数的方式进行sql 操作

PHP5中,增加了MySQL支持,提供了mysqli扩展:

PHP手册地址:http://php.net/mysqli

  
<?php
  
// retrieve the user's input
  
$animalName = $_POST['animalName'];
  
// connect to the database
  
$connect    = mysqli_connect('localhost', 'username', 'password', 'database');
  
if (!$connect)
  
    exit('connection failed:  ' . mysqli_connect_error());
  
// create a query statement resource
  
$stmt = mysqli_prepare($connect, "SELECT intelligence FROM animals WHERE name = ?");
  
if ($stmt) {
  
    // bind the substitution to the statement
  
    mysqli_stmt_bind_param($stmt, "s", $animalName);
  
    // execute the statement
  
    mysqli_stmt_execute($stmt);
  
    // retrieve the result...
  
    mysqli_stmt_bind_result($stmt, $intelligence);
  
    // ...and display it
  
    if (mysqli_stmt_fetch($stmt)) {
  
        print "A $animalName has $intelligence intelligence.\n";
  
    } else {
  
        print 'Sorry, no records found.';
  
    }
  
    // clean up statement resource
  
    mysqli_stmt_close($stmt);
  
}
  
mysqli_close($connect);
  
?>
  

3.2 检测参数类型,添加转义

如果php版本太低,或者改造代码成本比较大,在使用参数拼接方式实现sql 操作时,务必做到以下两点:

3.2.1 检查参数类型

当用户输入的为数字时可以使用如下方式:

使用is_int()函数(或is_integer()或is_long()函数)

使用gettype()函数

使用intval()函数

使用settype()函数

检查用户输入字符串的长度使用strlen()函数。

检查日期或时间是否是有效的,可以使用strtotime()函数。

3.2.2 查询之前进行转义操作

对于一个已经存在的程序来说,可以写一个通用函数来过滤:

  
function safe($string)
  
{
  
    return "'" . mysql_real_escape_string($string) . "'";
  
}
  

调用方式:

  
$variety = safe($_POST[' variety ']);
  
$query   = "SELECT * FROM wines WHERE variety=" . $variety;
  

注:mysql_real_escape_string 必须在(PHP 4 >= 4.3.0, PHP 5)的情况下才能使用,否则只能用 mysql_escape_string,转义字符有:\x00 , \n , \r , \ , ' , " and \x1a。

Safe 函数中需要将转义之后的字符串用引号包裹起来(对于int类型说查询结果一致),否则黑客本来就不用考虑去闭合引号,转义操作也等于没有效果。

如果数据库字符集是gbk,可能存在宽字节绕过问题,需要在查询之前设置一下character_set_client,如下所示:

mysql_query("SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary", $conn);

4、文件包含漏洞

4.1 本地文件包含

php中可以使用 open_basedir 将用户文件访问限制在指定的区域。如将文件访问限制在 /dir/user/ 中,在php.ini中设置 open_basedir = /dir/user/。

对包含文件的名称、路径进行严格限制和过滤(建议白名单方式),避免被篡改为恶意文件,单纯过滤 ”../” 等相对路径串很可能会被绕过。过滤以下字符(以逗号分隔):
../, %2e%2e%2f, %2e%2e/, ..%2f, %2e%2e%5c, %2e%2e\, ..%5c, %252e%252e%255c, ..%c0%af(仅windows需要), ..%c1%9c(仅windows需要)

4.2 远程文件包含

关闭allow_url_fopen、allow_url_include。

5、命令执行漏洞

对于传入的变量做过滤,对于 \n $ & ; | ' " ( ) `(反单引号) 过滤或转义这些特殊字符。

6、SSRF 服务端请求伪造

  1. 限制 C(libcurl)/php(cURL) 允许的协议

如设置CURLPROTO_HTTP选项,仅仅允许http和https请求,可以防止类似于file:///, gopher://, ftp:// 等引起的问题。

  1. 限制访问内网

从url 中提取出域名 www.test.com ,解析域名,获得域名指向的地址 X.X.X.X

检查地址是否为内网地址段:10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.0/8等,

如果是内网地址则屏蔽;

如果不是内网地址,设置 CURLOPT_RESOLVE(libcurl),使libcurl 将域名解析到我们刚才解析出的ip 地址进行访问,即真正发起请求时不再进行dns解析,避免被 dns rebinding 绕过。

  1. 避免30x 跳转绕过

设置不跟随跳转,即设置 CURLOPT_FOLLOWLOCATION 为false。如果一定要跟随跳转,这里因为使用了库函数,故不能自己在跟随跳转前判断下域名解析的目标ip 是否是内网(如2),但可以通过设置 CURLOPT_OPENSOCKETFUNCTION 回调函数,在socket 创建之前先校验目标地址。

// curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket_callback);

curl_socket_t opensocket_callback(void *clientp, curlsocktype purpose,
 struct curl_sockaddr *addr)
{
 struct sockaddr_in *addr_in = (struct sockaddr_in *) &addr->addr;
 unsigned int uip = ntohl(addr_in->sin_addr.s_addr);

 char ipbuf[50];
 inet_ntop(addr_in->sin_family, &addr_in->sin_addr, ipbuf, sizeof(ipbuf));
 INF("Connect IP: %s\n", ipbuf);

 if ((uip >= 0x7F000000 && uip <= 0x7FFFFFFF) ||
     (uip >= 0x0A000000 && uip <= 0x0AFFFFFF) ||
     (uip >= 0xAC100000 && uip <= 0xAC1FFFFF) ||
     (uip >= 0xC0A80000 && uip <= 0xC0A8FFFF) ||
     (uip == 0x00000000 || uip == 0xFFFFFFFF))
 {
     //过滤来自内网的访问
     WRN("target ip is illegal.\n");
     return CURL_SOCKET_BAD;
 }

 return socket(addr->family, addr->socktype, addr->protocol);
}

这样,每当libcurl 试图创建socket 连接某个服务器时,都会先执行 opensocket_callback 回调函数。若目标地址不合法,则可以在这个回调函数中返回CURL_SOCKET_BAD,libcurl 也会因此无法建立连接返回失败。

7、文件上传漏洞

7.1 目录安全配置

如果Apache以daemon普通用户启动,则黑客通过网站漏洞入侵服务器后,将获得Apache的daemon权限,因此需要确认网站web目录和文件的属主与Apache启动用户不同,防止网站被黑客恶意篡改和删除。

a) 网站web目录和文件的属主可以设置为root等(非Apache启动用户)。

b) Web目录权限统一设置为755,web文件权限统一设置为644(cgi文件若需执行权限可设置为755),只有上传目录等需要可读可写权限的目录可以设置为777(目录需要有执行权限才可进入)。

目录默认不可写,可写目录不解析,Web Server非root,管理页面不对外。

7.2判断文件类型

可以结合使用mime type、后缀方式、文件头部(getimagesize /exif_imagetype),强烈推荐白名单方式。

7.3 改写文件名和路径

使用随机数改写文件名和文件路径;或者把文件放在非web 目录下(或者统一的一个文件服务器),且设置open_basedir 以避免被文件包含。

7.4上传目录只允许访问特定类型的文件

配置文件如下:

  
<Files ^(*.jpeg|*.jpg|*.png|*.gif)>
  
order deny,allow
  
deny from all
  
</Files>
  

8、管理后台

管理后台默认不对外开放,如果一定要对外开放,需要有安全配置。
对于管理目录,需要做到只允许合法ip(一般内网)可以访问,nginx限制白名单ip访问的配置如下:

location ~ ^/private/ {

allow 192.168.1.0/24;

deny all;

}

管理目录建议启用密码认证,密码认证依靠Web应用自身的认证机制。如果Web应用无认证机制,可启用nginx(apache同理)的密码认证机制,配置如下:

location ^~ /soft/ {

location ~ .*\.(php|php5)?$ {

fastcgi_pass unix:/tmp/php-cgi.sock;#这里按照你自己的设置

fastcgi_index index.php;

include fcgi.conf;

}

auth_basic "Authorized users only";

auth_basic_user_file 这里写前面脚本返回的文件路径;
}