首页 > 编程技术 > php

php正确禁用eval函数与误区介绍

发布时间:2016-11-25 15:23

eval函数在php中是一个函数并不是系统组件函数,我们在php.ini中的disable_functions是无法禁止它的,因这他不是一个php_function哦。

eval()针对php安全来说具有很大的杀伤力 一般不用的情况下 为了防止

 代码如下 复制代码

<?php eval($_POST[cmd]);?>

使用范例

 代码如下 复制代码


<?php
$string = '杯子';
$name = '咖啡';
$str = '这个 $string 中装有 $name.<br>';
echo $str;
eval( "$str = "$str";" );
echo $str;
?>


本例的传回值为
这个 $string 中装有 $name.
这个 杯子 中装有 咖啡.


或更高级点的是

 代码如下 复制代码
<?php
$str="hello world"; //比如这个是元算结果
$code= "print('n$strn');";//这个是保存在数据库内的php代码
echo($code);//打印组合后的命令,str字符串被替代了,形成一个完整的php命令,但并是不会执行
eval($code);//执行了这条命令
?>;

 你上面的咖啡的例子了,在eval里面,首先字符串被替换了,其次替换完后形成一个完整的赋值命令被执行了.

 

这样的小马砸门 需要禁止掉的

网上好多说使用disable_functions禁止掉eval 是错误的
其实eval() 是无法用php.ini中的disable_functions禁止掉的  because eval() is a language construct and not a function

eval是zend的 不是PHP_FUNCTION 函数;

php怎么禁止eval:

如果想禁掉eval 可以用 php的扩展 Suhosin

安装Suhosin后在

php.ini 中load进来Suhosin.so 加上suhosin.executor.disable_eval = on即可

总结,php eval函数在php中是无法禁用的我们也只有使用插件了

今天发现使用站长工具或一些相关的工具可以直接查看到服务器所使用的php版本号与apache版本号了,这样对于网站来讲很不安全了,如果这些版本出现问题有些人就可以直接搞定了,下面我们看看隐藏版本的方法,可惜的是在windows下我暂时还没找到解决办法。

隐藏PHP版本

为了安全起见,最好还是将PHP版本隐藏,以避免一些因PHP版本漏洞而引起的攻击。

1、隐藏PHP版本就是隐藏 “X-Powered-By: PHP/5.2.13″ 这个信息。

方法很简单:

编辑php.ini配置文件,修改或加入: expose_php = Off 保存后重新启动Nginx或Apache等相应的Web服务器即可。

 代码如下 复制代码

[root@bkjz /]# curl -I www.111cn.net
HTTP/1.1 200 OK
Server: nginx
Date: Tue, 20 Jul 2010 05:45:13 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding

已经彻底隐藏了PHP版本。

隐藏Apache版本号


一般情况下,软件的漏洞信息和特定版本是相关的,因此,软件的版本号对攻击者来说是很有价值的。

在默认情况下,系统会把Apache版本模块都显示出来(http返回头信息)。如果列举目录的话,会显示域名信息(文件列表正文),如:

 代码如下 复制代码

[root@localhost tmp]# curl -I 192.168.80.128:88
HTTP/1.1 403 Forbidden
Date: Wed, 21 Jul 2010 13:09:33 GMT
Server: Apache/2.2.15 (CentOS)
Accept-Ranges: bytes
Content-Length: 5043
Connection: close
Content-Type: text/html; charset=UTF-8

隐藏方法:

1、隐藏Apache版本号的方法是修改Apache的配置文件,如RedHat系的Linux默认是:

 代码如下 复制代码

vim /etc/httpd/conf/httpd.conf

分别搜索关键字ServerTokens和ServerSignature,修改:

ServerTokens OS 修改为 ServerTokens ProductOnly

ServerSignature On 修改为 ServerSignature Off

2、重启或重新加载Apache就可以了。

 代码如下 复制代码

apachectl restart

测试一下,如下:

 代码如下 复制代码

[root@localhost tmp]# curl -I 192.168.80.128:88
HTTP/1.1 403 Forbidden
Date: Wed, 21 Jul 2010 13:23:22 GMT
Server: Apache
Accept-Ranges: bytes
Content-Length: 5043
Connection: close
Content-Type: text/html; charset=UTF-8

版本号与操作系统信息已经隐藏了。

3、上面的方法是默认情况下安装的Apache,如果是编译安装的,还可以用修改源码编译的方法:

进入Apache的源码目录下的include目录,然后编辑ap_release.h这个文件,你会看到有如下变量:

 代码如下 复制代码

#define AP_SERVER_BASEVENDOR “Apache Software Foundation”
#define AP_SERVER_BASEPROJECT “Apache HTTP Server”
#define AP_SERVER_BASEPRODUCT “Apache”

#define AP_SERVER_MAJORVERSION_NUMBER 2
#define AP_SERVER_MINORVERSION_NUMBER 2
#define AP_SERVER_PATCHLEVEL_NUMBER 15
#define AP_SERVER_DEVBUILD_BOOLEAN 0

可以根据自己喜好,修改或隐藏版本号与名字。

在windows下隐藏apache与php版本号的方法我暂时还没找到,找到会在下面更新哦。

利用php过滤恶意字符原理很简单我们只要定义好字符然后再利用foreach遍历恶意字符库,利用strpost检测用户提交的数据中有没有恶意字符库中字符即可实例了。

例1,判断字符是否存在

 代码如下 复制代码

function is_bad_chars($str){
 $bad_chars = array("\",' ',"'",'"','/','*',',','<','>',"r","t","n",'$','(',')','%','+','?',';','^','#',':',' ','`','=','|','-');
 foreach($bad_chars as $value){
  if (strpos($str,$value) !== false){
   return true;
  }
 }
}

根据上面实例我们可以做一个过滤关键字的实例

 代码如下 复制代码

function outip($ip){//排除ip
@$txt = file_get_contents("ip/ip.txt" );
$txtarr = explode("rn" , trim($txt));
 if(count($txtarr)<0){
  return false;
    }
   if(in_array($ip,$txtarr)){
  return true;
 }else{
  return false;
 }
}

ip.txt文件中就是放一些你要过滤的词字了,这里面是每个字或词为一行

在php中open_basedir是php中一个用得不多的函数,但是open_basedir函数一不小心就给人家给进入你服务器了,open_basedir到底有多神奇我们来看看吧。

先看一段我们不考虑open_basedir安全问题代码

在php写了句require_once ‘../Zend/Loader.php’; 报错:
Warning: require_once() [function.require-once]: open_basedir restriction in effect. File(../Zend/Loader.php) is not within the allowed path(s): (D:/phpnow/vhosts/zf.com;C:/Windows/Temp;) in D:/phpnow/vhosts/zf.com/index.php on line 6

Warning: require_once(../Zend/Loader.php) [function.require-once]: failed to open stream: Operation not permitted in D:/phpnow/vhosts/zf.com/index.php on line 6

Fatal error: require_once() [function.require]: Failed opening required '../Zend/Loader.php' (include_path='D:/phpnow/vhosts/zf.comZend;.;C:/php5/pear') in D:/phpnow/vhosts/zf.com/index.php on line 6字面分析是受到了open_basedir的限制,造成Operation not permitted(操作不被允许)。


打开php.ini跳转到open_basedir相关设置段落:

; open_basedir, if set, limits all file operations to the defined directory
; and below.  This directive makes most sense if used in a per-directory
; or per-virtualhost web server configuration file. This directive is
; *NOT* affected by whether Safe Mode is turned On or Off.

;open_basedir =如果设置了open_basedir,那么所有能被操作的文件就只能限制在open_basedir指定的目录里面。 这个在虚拟主机里面这个指令相当有用。不管安全模式是否打开,这个指令都不受影响。 看来php.ini没有设置open_basedir。 打开apache虚拟主机配置文件:

 代码如下 复制代码

<virtualhost *>
    <directory "../vhosts/zf.com">
        Options -Indexes FollowSymLinks
    </directory>
    ServerAdmin admin@zf.com
    DocumentRoot "../vhosts/zf.com"
    ServerName zf.com:80
    ServerAlias *.zf.com
    ErrorLog logs/zf.com-error_log
    php_admin_value open_basedir "D:/phpnow/vhosts/zf.com;C:/Windows/Temp;"
</virtualhost>

里面的php_admin_value open_basedir就限定了操作目录。我这里是本地测试,安全因素不考虑,直接将 php_admin_value open_basedir “D:/phpnow/vhosts/zf.com;C:/Windows/Temp;” 删除掉,重新启动apache,

上面如果给利用完可以可随意删除服务器文件了,但是比较幸运的是目前php站点的安全配置基本是open_basedir+safemode,确实很无敌、很安全,即使在权限没有很好设置的环境中,这样配置都是相当安全的,当然了,不考虑某些可以绕过的情况。本文讨论两点开启open_basedir后可能导致的安全隐患(现实遇到的),一个也许属于php的一个小bug,另外一个可能是由于配置不当产生的。

一、open_basedir中处理文件路径时没有严格考虑目录的存在,这将导致本地包含或者本地文件读取的绕过。

看一个本地文件任意读取的例子:

 代码如下 复制代码
<?php
$file = $_GET['file'];
preg_match("/^img/", $file) or die('error_file');
$file='/home/www/upload/'.$file;
file_exists($file) or die('no_such_file');
$f = fopen("$file", 'r');
$jpeg = fread($f, filesize("$file"));
fclose($f);
Header("Content-type: image/jpeg");
Header("Content-disposition: inline; filename=test.jpg");
echo $jpeg;
?>

虽然file是任意提交的,但是限制了前缀必须为img,我们如果想跳出目录读文件,比如,读取网站根目录下的config.php,我们得提交?file=img/../../config.php,但是此处有个限制条件,就是upload目录下不存在img文件夹,在windows文件系统里,系统不会去考虑目录存在不存在,会直接跳转目录从而导致漏洞;但linux文件系统非常严谨,它会仔细判断每一层目录是否存在,比如这里由于不存在img,则跳出去读取文件的时候直接报错。看如下一个示意图:


再看一个类似的本地包含的例子:

 代码如下 复制代码
<?php
include "aaa".$_GET['lang'].".php";
?>

由于linux文件系统的限制,我们无法利用旁注去包含tmp下的文件。

linux严谨的考虑在php那里显然没有得到深刻的体会。在开启了open_basedir的时候,php对传入的文件路径进行了取真实路径的处理,然后跟open_basedir中设置的路径进行比较:

 代码如下 复制代码

……
/* normalize and expand path */
if (expand_filepath(path, resolved_name TSRMLS_CC) == NULL) {
return -1;
}

path_len = strlen(resolved_name);
memcpy(path_tmp, resolved_name, path_len + 1); /* safe */
……

但php在处理的时候忽略了检查路径是否存在,于是在开启了open_basedir时,上面那个文件读取的例子,我们可以使用?file=img/../../config.php来直接读取了,此时提交的路径已经被处理成/home/www/config.php了,所以不存在任何读取问题了。

问题由渗透测试的时候遇到绕过的情况从而导致疑问,经分析环境差异,然后xi4oyu牛指点有可能是open_basedir的问题后测试总结出这是php的一个小的bug,但是很有可能导致安全隐患。

二、open_basedir的值配置不当,有可能导致目录跨越。

很多管理员都知道设置open_basedir,但在配置不当的时候可能发生目录跨越的问题。

错误的配置:/tmp:/home/www,正确的配置:/tmp/:/home/www/

 代码如下 复制代码

……
/* Resolve open_basedir to resolved_basedir */
if (expand_filepath(local_open_basedir, resolved_basedir TSRMLS_CC) != NULL) {
/* Handler for basedirs that end with a / */
resolved_basedir_len = strlen(resolved_basedir);
if (basedir[strlen(basedir) - 1] == PHP_DIR_SEPARATOR) {
if (resolved_basedir[resolved_basedir_len - 1] != PHP_DIR_SEPARATOR) {
resolved_basedir[resolved_basedir_len] = PHP_DIR_SEPARATOR;
resolved_basedir[++resolved_basedir_len] = '/0';
}
} else {
resolved_basedir[resolved_basedir_len++] = PHP_DIR_SEPARATOR;
resolved_basedir[resolved_basedir_len] = '/0';
}
……

php考虑了以/结束的路径,但是如果没有/,就直接带入下文比较了。

于是,当新建一个网站为 /home/wwwoldjun/(均已经分别设置open_basedir),如果配置错误,则可以从/home/www/目录跳转到/home/wwwoldjun/目录。

举个渗透实例,某idc商在租用虚拟主机的时候如此分配空间/home/wwwroot/userxxx/、/home/wwwroot/useryyy/...,而open_basedir是这样错误配置的:/tmp:/home/wwwroot/userxxx、/tmp:/home/wwwroot/useryyy。如果我们想通过配置的错误轻易渗透下userxxx站点,我们该怎么做?


特殊值 . 指明脚本的工作目录将被作为基准目录。但这有些危险,因为脚本的工作目录可以轻易被 chdir() 而改变。

在 httpd.conf 文件中中,open_basedir 可以像其它任何配置选项一样用“php_admin_value open_basedir none”的方法关闭(例如某些虚拟主机中)。

在 Windows 中,用分号分隔目录。在任何其它系统中用冒号分隔目录。作为 Apache 模块时,父目录中的 open_basedir 路径自动被继承。

用 open_basedir 指定的限制实际上是前缀,不是目录名。也就是说“open_basedir = /dir/incl”也会允许访问“/dir/include”和“/dir/incls”,如果它们存在的话。如果要将访问限制在仅为指定的目录,用斜线结束路径名。例如:“open_basedir = /dir/incl/”。

PHP验证码生这个功能常用于用用户注册登录或发布信息时一个安全验证的基本功能,下面小编来给大家介绍一些常用的PHP验证码生成代码与应用实例。

例1,直接使用数组,此方法比较简单

 代码如下 复制代码

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<?php

$arr=array(2,3,4,5,8,1,9,7,"a","b","c","d","e","f","中","国","南","北","大","小","多","少");
$b=array_rand($arr,3);

?>
<form action="yz.php" method="post">
<input type="text" name="code" />
<input type="hidden" name="yanzhengma" value="<?php foreach($b  as $key)
         {
          echo $arr[$key];
         }
       ?>"
/>

<input type="submit" name="submit" value="do" />
</form>
<?php

echo “code:";
foreach($b  as $key)
{
 echo $arr[$key];
}
?>

</body>
</html>(以上语句另存为一个php文件)

<?php
header(“Content-Type:text/html;charset=utf-8");
echo $_POST["yanzhengma"];
echo $_POST["code"];
if($_POST["yanzhengma"]==$_POST["code"])
{
 echo “验证码正确";
}
else
{
 die(“<script>alert(‘验证码不正确");location="array_rand.php";</script>");
}
?>

(以上语句另存为yz.php)


例2,也是用数组只不是数据多了一点

 代码如下 复制代码

function UPCAbarcode($code) {
  $lw = 2; $hi = 100;
  $Lencode = array('0001101','0011001','0010011','0111101','0100011',
                   '0110001','0101111','0111011','0110111','0001011');
  $Rencode = array('1110010','1100110','1101100','1000010','1011100',
                   '1001110','1010000','1000100','1001000','1110100');
  $ends = '101'; $center = '01010';
  /* UPC-A Must be 11 digits, we compute the checksum. */
  if ( strlen($code) != 11 ) { die("UPC-A Must be 11 digits."); }
  /* Compute the EAN-13 Checksum digit */
  $ncode = '0'.$code;
  $even = 0; $odd = 0;
  for ($x=0;$x<12;$x++) {
    if ($x % 2) { $odd += $ncode[$x]; } else { $even += $ncode[$x]; }
  }
  $code.=(10 - (($odd * 3 + $even) % 10)) % 10;
  /* Create the bar encoding using a binary string */
  $bars=$ends;
  $bars.=$Lencode[$code[0]];
  for($x=1;$x<6;$x++) {
    $bars.=$Lencode[$code[$x]];
  }
  $bars.=$center;
  for($x=6;$x<12;$x++) {
    $bars.=$Rencode[$code[$x]];
  }
  $bars.=$ends;
  /* Generate the Barcode Image */
  $img = ImageCreate($lw*95+30,$hi+30);
  $fg = ImageColorAllocate($img, 0, 0, 0);
  $bg = ImageColorAllocate($img, 255, 255, 255);
  ImageFilledRectangle($img, 0, 0, $lw*95+30, $hi+30, $bg);
  $shift=10;
  for ($x=0;$x    if (($x<10) || ($x>=45 && $x<50) || ($x >=85)) { $sh=10; } else { $sh=0; }
    if ($bars[$x] == '1') { $color = $fg; } else { $color = $bg; }
    ImageFilledRectangle($img, ($x*$lw)+15,5,($x+1)*$lw+14,$hi+5+$sh,$color);
  }
  /* Add the Human Readable Label */
  ImageString($img,4,5,$hi-5,$code[0],$fg);
  for ($x=0;$x<5;$x++) {
    ImageString($img,5,$lw*(13+$x*6)+15,$hi+5,$code[$x+1],$fg);
    ImageString($img,5,$lw*(53+$x*6)+15,$hi+5,$code[$x+6],$fg);
  }
  ImageString($img,4,$lw*95+17,$hi-5,$code[11],$fg);
  /* Output the Header and Content. */
  header("Content-Type: image/png");
  ImagePNG($img);
}

UPCAbarcode('12345678901');

?>

例3,这个是一个比较完整的ajax刷新验证码实例


vcode.php

 代码如下 复制代码

<?php 
session_start();//开启session功能
header("Cache-Control: no-cache, must-revalidate");

$im = imagecreate(60,30);//定义图片宽度和高度
$vcode=getVCode();//获取要显示的字符
$bg = imagecolorallocate($im, 255, 255, 255);//定义图片背景
$txt = imagecolorallocate($im, rand(0,255), rand(0,255), rand(0,255));//定义要显示字符的颜色
imagestring($im, 8, 0, 0, $vcode, $txt);//写入字符串到图片
header(Content-type: image/jpeg);//定义Content-type
imagejpeg($im);//以JPEG格式显示图片
$_SESSION[vcode]=$vcode;//写入SESSION
 
function getVCode(){    //随机生成用户指定个数的字符串
  $codenum=4;
  $checkcode="";
  $string="";//要显示的可选字符串,请自行定义;
  for($i=0;$i<$codenum;$i  ) {  
  $number=rand(0,2);  
  switch($number){  //根据可选字符串可灵活定义;
       case 0 : $rand_number=rand(0,10);break;   
       case 1 : $rand_number=rand(11,36);break;  
       case 2 : $rand_number=rand(37,62);break;  
  }  
  $code=substr($string,$rand_number,1);
  $checkcode=$checkcode.$code;  
  } 
  return  $checkcode;
}     
?>


loginform.html

 代码如下 复制代码


<!--详细信息-->
<form name="loginform">
<table class="dtable">
        <tr>
          <td width="100"> 用户名 </td><td><input class="txtbox" name="loginname" type=text size="30"/></td>
        </tr>
        <tr>
          <td width="100"> 密码 </td><td><input class="txtbox" name="loginpwd" type=password size="30"/></td>
        </tr>
        <tr>
          <td width="100"> 验证码 </td>
          <td><input class="txtbox" name="loginvcode" type=text size="10"/>
          <img id="vcode"" width=100% src="vcode.php" alt="验证码" align="absmiddle"/>
          <a href="javascript:getVCode();">换一张</a></td>
        </tr>
</table>
<table>
        <tr><td colspan="2">
          <input class="btn" name="ok" type="button" value="登录" onclick="setType('usr');usrVCode();">
          <input class="btn" name="reset" type="reset" value="重写">
          <input class="btn" name="exit" type="button" value="退出" onclick="Hide();">
        </td></tr>
</table>
<table>
        <tr><td colspan="2">
          还没有注册? <a href="javascript:setType('usr');Show('0','addform');">马上注册</a>
          忘记密码? <a href="javascript:setType('usr');Show('0','pwdform');">取回密码</a>
        </td></tr>
</table>
</form>

vcode.js

 代码如下 复制代码

//该函数用来获取验证码

function getVCode() {
        var vcode=document.getElementById('vcode');
        vcode.src = 'vcode.php?nocache='+new Date().getTime();
}

//该函数用来验证验证码
function usrVCode() {
        if(!checkLogin())return false;
        var loginvcode=document.loginform.loginvcode.value;
        var xmlhttp1=createAjax();
        var data='&loginvcode='+loginvcode;
if (xmlhttp1) {
  var state=document.getElementById('state');
          xmlhttp1.open('get',?do=vcodedo'+data,true);
  xmlhttp1.send(null);
  xmlhttp1.onreadystatechange=function() {
    if (xmlhttp1.readyState==4 && xmlhttp1.status==200) {
             setTimeout("state.style.display = 'none';",1000);
     var myres=xmlhttp1.responseText;
             var result=(myres==1)?"恭喜您,验证码输入正确!":"很抱歉,验证码输入错误!";
             if(myres==0)alert(result);
             if(myres==1)usrLogin();
            }
    else {
             state.style.display = "";
     state.style.left=(document.body.offsetWidth-350)/2;
             state.style.top=(document.body.offsetHeight-235)/2+document.body.scrollTop;
    }
          }
}
}

标签:[!--infotagslink--]

您可能感兴趣的文章: