Fortify漏洞修复总结
1.代码注入
1.1 命令注入
命令注入是指应用程序执行命令的字符串或字符串的一部分来源于不可信赖的数据源,程序没有对这些不可信赖的数据进行验证、过滤,导致程序执行恶意命令的一种攻击方式。
问题代码:
$dir = $_POST['dir']
exec("cmd.exe /c dir" + $dir);
修复方案:
(1)程序对非受信的用户输入数据进行净化,删除不安全的字符。
(2)限定输入类型, 创建一份安全字符串列表,限制用户只能输入该列表中的数据。 修复例子:
//方式1 if (!preg_match('/^[\w\.:\-]+$/', $dir) ){ // Handle error } $cmd = filter_var($cmd, FILTER_VALIDATE_REGEXP, array("options" => array("regexp" => getCommandFilterReg()))); ... $msg = escapeshellarg($msg); //方式2 function cmd_arg($cmd, $filter='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.') { $filter_chars = str_split($filter); $filter_chars = array_combine($filter_chars, $filter_chars); $cmd_chars = str_split($cmd); $ret = ''; foreach ($cmd_chars as $v) { $ret .= isset($filter_chars[$v]) ? $filter_chars[$v] : ''; } return $ret; } $cmd = cmd_arg($cmd);
1.2 js动态代码注入
(1)主要是前端使用了eval函数来解析服务端的响应
evalResponse: function() { try { return eval((this.transport.responseText )); } catch (e) { this.dispatchException(e); }
修复方案: a.不要使用eval函数,使用自定义函数替换
function _dhtmlxEvalData( code ) { var script; if ( code ) { var data_key = '_process_json_data_'+parseInt( rand(0,1000000000000)); code = 'window["'+data_key+'"]=('+code+');' // If the code includes a valid, prologue position // strict mode pragma, execute code by injecting a // script tag into the document. script = document.createElement("script"); script.text = code; document.head.appendChild( script ).parentNode.removeChild( script ); return window[data_key]; } return null; } return _dhtmlxEvalData(this.transport.responseText );
(2)document.write(html)中写入的html和document.location = url 中的url没有过滤处理
var html = '<span>'+rows[i]+'</span>'; document.write(html) .... document.location = url
修复方案: a.避免使用document.write 参考地址
b.使用白名单
//document.write() 换成如下写法 _var sNew = document.createElement("script"); sNew.async = true; sNew.src = "https://example.com/script.min.js"; var s0 = document.getElementsByTagName('script')[0]; s0.parentNode.insertBefore(sNew, s0); //document.location = url的处理 function safe_url (url, whiteChars) { whiteChars = ''+(whiteChars||'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-_~+#,%&=*;:@[]'); var ret = ''; for(var i=0; i<url.length; i++) { ret += whiteChars[whiteChars.indexOf(url[i])] || ''; } do { var old = ret; ret = ret.replace(/javascript:/gi,''); }while(ret != old); return ret; } document.location = safe_url(url);
(3) 接收的全局变量和setTimeout函数相遇
问题代码:
this.timer = setTimeout(this.onTimerEvent.bind(this), this.decay * this.frequency * 1000); ... (u = setTimeout(function() { x.abort("timeout") }, c.timeout));
修复方案: 使用匿名函数,包裹setTimeout函数
(function(){ this.timer = setTimeout(this.onTimerEvent.bind(this), this.decay * this.frequency * 1000); })(); ... (u = (function() { var u = setTimeout(function() { x.abort("timeout") }, c.timeout); return u; })() );
1.3 JSON 注入
问题代码:
$data = file_get_contents("php://input"); $data = json_decode($data, true);
修复方案: 使用filter_var函数进行过滤 修复例子:
$data = file_get_contents("php://input"); $data = filter_var($data, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES); $data = json_decode($data, true);
1.4 SQL注入
SQL injection 发生原因:
1、数据从一个不可信赖的数据源进入程序。
2、数据用于动态地构造一个 SQL 查询。
问题代码:
$sql = "SELECT value FROM config WHERE ip=".$ip." AND owner=".$_SESSION["user"]; $stm->execute(); $stm->fetchAll(PDO::FETCH_ASSOC);
修复方案:
a、确保sql语句不通过拼接的方式构造;
b、然后采用一些对sql语句进行预编译的执行方法;
c、最后再以参数绑定的方式设置sql语句需要的条件的值。
修复例子:
$sql = "SELECT value FROM config WHERE ip=? AND owner=?"; $stm = $db->prepare($sql); $stm->execute(array($ip, $_SESSION["user"])); $rows = $stm->fetchAll(PDO::FETCH_ASSOC);
2.不安全随机数
标准的伪随机数值生成器不能抵挡各种加密攻击。伪随机数生成器 (PRNG) 近似于随机算法,是统计学的 PRNG,该PRNG是可移植和可重复的,攻击者很容易猜到其生成的字符串。 在对安全性要求较高的环境中,使用能够生成可预测值的函数作为随机数据源,会产生 Insecure Randomness 错误。
2.1 php例子
代码中使用了rand() 和mt_rand() 函数,相关了解
mt_srand(time()); $token = mt_rand(); ... $randnum = rand(1,10000); $str = md5($token.$randnum.time()); ...
修复方案: 1.php7 添加了更好的随机数random_int()用来代替php5的mt_rand()
2.使用openssl_random_pseudo_bytes函数重新自定义随机函数,注意运行环境需要支持该函数
修复例子:
f
unction dynamicNumber($min,$max) { $range = $max - $min + 1; if ($range == 0) return $min; $length = (int) (log($range,2)/8) + 1; $max = pow(2, 8 * $length); $num = $max + 1; while ($num > $max) { $num = hexdec(bin2hex(openssl_random_pseudo_bytes($length,$s))); } return ($num % $range) + $min; } function getDynamicInt($min = 0, $max = null) { if ($max === null) { $max = getrandmax(); } return dynamicNumber($min,$max); } function getDynamicMtInt($min = 0, $max = null) { if ($max === null) { $max = mt_getrandmax(); } return dynamicNumber($min,$max); } $token = getDynamicMtInt(); ... $randnum = getDynamicInt(1,10000);
2.2 js例子
代码中使用了Math.random()
... var rnd = Math.random(); ...
修复方案: 不使用Math.random(),原理参考 自定义随机函数 修复例子:
var rand = (function(){ var seed = (new Date()).getTime() function r(){ seed = (seed*9301+49297)%233280 return seed/(233280.0) } return function(number){ return Math.ceil(r()*number) } })() console.log(rand(5)); function randnum() { var seed = (new Date()).getTime(); seed = (seed*9301+49297)%233280; return seed/(233280.0); }
3.硬编码密码
程序中采用硬编码方式处理密码,一方面会降低系统安全性,另一方面不易于程序维护。
if (password == null) { password = "123456"; }
fortify可能会误报,比如一些带关键词的变量:password、passwd、pass、password_xxx、xxx_passwd等
修复方式: 程序中所需密码应从配置文件中获取经过加密的密码值。对于误报的变量,只有修改变量名。
4.其他
1.空密码问题
其实只是变量设置为空,但是fortify要报错
$passwd = ""; //解决方式用函数转为空 $passwd = strval(null);
2.变量覆盖
extract($params) //改为 extract($params,EXTR_SKIP);
3.base64_pri_decrypt() 方法执行不带 OAEP 填充模式的公钥 RSA 加密,因此加密机制比较脆弱。
openssl_public_encrypt($input, $output, $key, OPENSSL_NO_PADDING); =》 openssl_private_decrypt($password_base64_decode, $password_decode, $pi_key,OPENSSL_PKCS1_OAEP_PADDING);//私钥解密
4.js中的setTimeout 报动态代码注入
this.timer = setTimeout(this.onTimerEvent.bind(this), this.decay * this.frequency * 1000);
用匿名函数将其包裹
(function(){ this.timer = setTimeout(this.onTimerEvent.bind(this), this.decay * this.frequency * 1000); })();
例2:
(u = setTimeout(function() { x.abort("timeout") }, c.timeout)); 改为
(u = (function() { var u = setTimeout(function() { x.abort("timeout") }, c.timeout); return u; })() );
5.Cookie Security: Overly Broad Path path 不传“/”
6..xss,css Dom
safe_url: function (url, whiteChars) {
whiteChars = ''+(whiteChars||'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-_~+#,%&=*;:@[]');
var ret = ''; for(var i=0; i<url.length; i++) { ret += whiteChars[whiteChars.indexOf(url[i])] || ''; } do { var old = ret; ret = ret.replace(/javascript:/gi,''); }while(ret != old); return ret; },
7.jsonencode的输出 filter_var_array()
function stripHtml(value) { // remove html tags and space chars return value.replace(/<.[^<>]*?>/g, " ").replace(/ | /gi, " ") // remove punctuation .replace(/[.(),;:!?%#$'\"_+=\/\-“”’]*/g, ""); }