集美阅读大全是一个以文章句子为主题的在线阅读网站。内含有各种经典好文章,爱情美文,诗歌散文,情感句子说说,范文资料等。读好文章,尽在集美阅读大全!!!
当前位置:集美阅读大全 >互联网 >建站 > 正文

关于PHP配置和代码编写不规范造成的安全问题

2019-09-26 03:38编写 造成 配置 规范 代码

php因天生支持web应用的开发,以其简单易学,开发效率高而备受喜爱。使其占据了大片的市场。但是php本身的安全问题却一直不曾消停,以及不规范的php代码编写规范,使得web应用漏洞百出。这篇文章从配置文件和代码编写角度出发,总结记录php相关安全。

关于PHP配置和代码编写不规范造成的安全问题

 

一、充分了解你的php

1、基本信息

注意到以下的文件结构在新版本php或者不同的发行版中略有不同,就好比在ubuntu18.04中安装php7就和下面的文件结构有较大的差别,所以下面的文件仅仅作为一个apache的架构参考。

  • Root:/var/www/html
  • 默认Web服务:Apache(可以使用Lighttpd或Nginx代替)
  • 默认PHP配置文件:/etc/php.ini
  • 默认PHP Extensions
  • 配置目录:/etc/php.d/
  • PHP安全配置样例文件:/etc/php.d/security.ini(需要使用文本编辑器创建这个文件)
  • php 版本: php -v
  • 查看当前PHP所编译 : php -m

 

2、敏感配置

以下是一些常见的配置举例,更多请查看:

http://php.net/manual/zh/ini.core.php#ini.variables-order。

不在请求头中泄露php信息:

  1. expose_php=Off

不回显php错误(包括运行错误时和启动时错误),但是进行错误记录:

  1. play_errors=Off
  2. display_startup_errors=off
  3. log_errors=On
  4. error_log=/var/log/httpd/php_scripts_error.log

文件上传开启与否和最大上传文件限制:

  1. file_uploads=On
  2. upload_max_filesize=1M

控制最大post数据:

  1. post_max_size=1M

注意:到要比upload_max_filesize大,否则后者失效。

关闭远程代码执行:

  1. allow_url_fopen=Off
  2. allow_url_include=Off

关闭全局注册变量,不过默认5.x版本的php是off:

  1. register_globals=off

关于安全模式和粗暴的魔术引号过滤,注意到save_mode模式在php5.3以上版本,safe_mode被弃用,在php5.4以上版本,则将此特性完全去除了:

  1. safe_mode=On
  2. safe_mode_include_dir = D:/phpstudy/www/include/
  3. magic_quotes_gpc=Off   #如果开启了这个,然后在php应用中使用addslashes()过滤输入会造成双重转义,使得过滤无济于事,遇到这种情况时可以使用函数   get_magic_quotes_gpc() 进行检测。
  4. magic_quotes_runtime

资源管理防止过分消耗服务器资源:

  1. max_execution_time = 30
  2. max_input_time = 30
  3. memory_limit = 40M

禁用危险函数:

  1. disable_functions =
  2. phpinfo,eval,passthru,assert,exec,system,ini_set,ini_get,get_included_files,
  3. get_defined_functions,get_defined_constants,get_defined_vars,
  4. glob,``,chroot,scandir,chgrp,chown,shell_exec,proc_open,proc_get_status,
  5. ini_alter,ini_restore,dl,pfsockopen,openlog,syslog,readlink,
  6. symlink,popepassthru,stream_socket_server,fsocket,fsockopen

限制php访问文件系统:

  1. open_basedir='/var/www/html/';......;......

session保存路径:

  1. session.save_path="/var/lib/php/session"

上传文件默认路径:

  1. upload_tmp_dir="/var/lib/php/upload"

 

3、关于危险函数

特殊符号:

“:反引号运算符在激活了安全模式或者关闭了 shell_exec() 时是无效的,同时与其它某些语言不同,反引号不能在双引号字符串中使用。否则将会当作shell命令执行,执行效果等同于shell_exec()。

  • 文件操作:http://php.net/manual/zh/ref.filesystem.php。
  • 全局信息,配置等:http://php.net/manual/zh/ref.info.php。
  • 程序执行:http://php.net/manual/zh/book.exec.php。

 

二、不要过分相信php

1、弱类型

前人之述备矣,仅仅做个汇总。同样还可以参看官网给出的类型表(PHP 类型比较表)。

  1. 0=='0'        //true 
  2. 0 == 'abcdefg'    //true 
  3. 1 == '1abcdef'    //true 
  4. null==false     //true 
  5. 123=='123'      //true  
  6. //哈希比较 
  7. "0e132456789"=="0e7124511451155" //true 
  8. "0e123456abc"=="0e1dddada"    //false 
  9. "0e1abc"=="0"     //true 
  10. "0x1e240"=="123456"        //true 
  11. "0x1e240"==123456        //true 
  12. var_dump(intval('2'))    //2 
  13. var_dump(intval('3abcd'))    //3 
  14. var_dump(intval('abcd'))    //0 
  15. //任意两个array,MD5相等 
  16. var_dump(md5($array1)==var_dump($array2));    //true 
  17. //case 自转换,以下代码输出i is less than 3 but not negative 
  18. $i ="2abc";
  19. switch ($i) {
  20. case 0:
  21. case 1:
  22. case 2:
  23.     echo "i is less than 3 but not negative";
  24.     break;
  25. case 3:
  26.     echo "i is 3";
  27. }
  28. //in_array的缺陷,array_search 
  29. $array=[0,1,2,'3'];
  30. var_dump(in_array('abc', $array));  //true 
  31. var_dump(in_array('1bc', $array));    //true 
  32. //strcmp在php5.x个版本后有些特性不太同,所以遇到的时候具体讨论 

 

2、全局注册变量

如果已经弃用的 register_globals 指令被设置为 on 那么局部变量也将在脚本的全局作用域中可用。例如, $_POST['foo'] 也将以 $foo 的形式存在。这将会造成一些变量覆盖,条件判断绕过。以下是简化的全局变量认证绕过模型:

  1. if(authenticated_user()){
  2.     $authorized=true;
  3. }
  4. if($authorized){
  5.     do something......
  6. }

对于以上的绕过,我们可以有以下的规避措施:(1) php.ini register_globals=off(2) 在每次判断前初始化变量,如下:

  1. $authorized=false;
  2. if(authenticated_user()){
  3.     $authorized=true;
  4. }
  5. if($authorized){
  6.     do something......
  7. }

 

3、php伪协议

伪协议在很多绕过场景下发挥着举足轻重的作用,如后面提到的文件包含file://协议绕过,以及最近才提出的phar协议反序列化对象注入,我们可以在不存在可控unserialization()函数的情况下利用phar反序列化对象,实现对象注入。所以在web应用中不要忽视他们的存在,千里之堤,溃于蚁穴。

  • file:///var/www/html 访问本地文件系统
  • ftp://:@ 访问FTP(s) URLs
  • data:// 数据流
  • http:// — 访问 HTTP(s) URLs
  • ftp:// — 访问 FTP(s) URLs
  • php:// — 访问各个输入/输出流
  • zlib:// — 压缩流
  • data:// — Data (RFC 2397)
  • glob:// — 查找匹配的文件路径模式
  • phar:// — PHP Archive
  • ssh2:// — Secure Shell 2
  • rar:// — RAR
  • ogg:// — Audio streams
  • expect:// — 处理交互式的流

 

三、向DVWA学习php安全的代码编写

以下样例来自于DVWA v1.9版本

1、sql注入

(1) Low level

  1. <?php
  2. if( isset( $_REQUEST[ 'Submit' ] ) ) {
  3.     // Get input  
  4.     $id = $_REQUEST[ 'id' ];
  5.     // Check database  
  6.     $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
  7.     $result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );
  8.     // Get results  
  9.     $num = mysql_numrows( $result );
  10.     $i   = 0;
  11.     while$i < $num ) {
  12.         // Get values  
  13.         $first = mysql_result( $result$i"first_name" );
  14.         $last  = mysql_result( $result$i"last_name" );
  15.         // Feedback for end user  
  16.         echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
  17.         // Increase loop count  
  18.         $i++;
  19.     }
  20.     mysql_close();
  21. }
  22. ?>

在这个例子中,是最low安全等级的php代码编写样例,可以看到,代码中并没有对用户输入的id变量进行检查和过滤,同时使用的是$_REQUEST全局数组的方式,如果不是特别需要,我们编程的时候尽量不要使用$_REQUEST获取用户的参数,因为$_REQUEST的参数比较杂,包括$_GET,$_POST,$_COOKIE等超全局变量,并且二者还存在变量获取顺序的不一致,受配置文件中variables_order的约定,在存在waf的环境下,容易造成绕过。未经处理的用户输入直接与sql语句拼接交互,造成sql注入漏洞,十分危险。

 

(2) Medium level

  1. <?php
  2. if( isset( $_POST[ 'Submit' ] ) ) { // Get input $id = $_POST[ 'id' ]; $id = mysql_real_escape_string( $id ); 
  3. // Check database  
  4. $query  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
  5. $result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );
  6. // Get results  
  7. $num = mysql_numrows( $result );
  8. $i   = 0;
  9. while$i < $num ) {
  10.     // Display values  
  11.     $first = mysql_result( $result$i"first_name" );
  12.     $last  = mysql_result( $result$i"last_name" );
  13.     // Feedback for end user  
  14.     echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
  15.     // Increase loop count  
  16.     $i++;
  17. }
  18. //mysql_close(); 
  19. }
  20. ?>

这个版本的代码,与之前的相比只是多了个mysql_real_escape_string函数的过滤,但是要知道这里的$id在sql语句中是数字类型,这样mysql_real_escape_string的转义就会形同虚设,注入仍旧是一马平川。当然不恰当的字符编码,可能会造成宽字节注入。

 

(3) High leval

  1. <?php
  2. if( isset( $_SESSION [ 'id' ] ) ) {
  3.     // Get input  
  4.     $id = $_SESSION[ 'id' ];
  5.     // Check database  
  6.     $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
  7.     $result = mysql_query( $query ) or die( '<pre>Something went wrong.</pre>' );
  8.     // Get results  
  9.     $num = mysql_numrows( $result );
  10.     $i   = 0;
  11.     while$i < $num ) {
  12.         // Get values  
  13.         $first = mysql_result( $result$i"first_name" );
  14.         $last  = mysql_result( $result$i"last_name" );
  15.         // Feedback for end user  
  16.         echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
  17.         // Increase loop count  
  18.         $i++;
  19.     }
  20.     mysql_close();
  21. }
  22. ?>

在高级版本中只是把注入点隐匿在了$_SESSION全局变量里面,而session中的id值的注册是通过用户输入$_POST全局变量传入,所以是完全可控的,这样一来,就和之前的注入没有什么不一样。这段代码是要提醒我们对于session,只要注册值是用户可控的,也是可能存在sql注入的风险的。另外需要注意到的是,在这个High级别的注入中,回显和传参页面不是同一个,是一个二阶注入,如果使用工具注入,如sqlmap,别忘了加上自定义回显–second-order参数。

 

(4) Impossible level

  1. <?php
  2. if( isset( $_GET[ 'Submit' ] ) ) {
  3.     // Check Anti-CSRF token  
  4.     checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
  5.     // Get input  
  6.     $id = $_GET[ 'id' ];
  7.     // Was a number entered?  
  8.     if(is_numeric$id )) {
  9.         // Check the database  
  10.         $data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
  11.         $data->bindParam( ':id', $id, PDO::PARAM_INT );
  12.         $data->execute();
  13.         $row = $data->fetch();
  14.         // Make sure only 1 result is returned  
  15.         if$data->rowCount() == 1 ) {
  16.             // Get values  
  17.             $first = $row[ 'first_name' ];
  18.             $last  = $row[ 'last_name' ];
  19.             // Feedback for end user  
  20.             echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
  21.         }
  22.     }
  23. }
  24. // Generate Anti-CSRF token  
  25. generateSessionToken();
  26. ?>

根据DVWA的说法,这样写出来的应用,是不存在sql注入的。也即这是个十分安全的php代码编写规范。why?首先,我们可以看到它使用Anti-CSRF token的方法来避免csrf攻击(具体细节会在下文csrf防御谈到),然后在sql语句的编写中,使用的是预处理语句,所谓的预处理就是通过php的pdo预处理机制PDO::prepare,先往数据库送出语句模板,进行解析,编译,然后第二次向数据库传入查询参数,在第二次的查询过程中可以理解为不再进行语义解析,所以即使传入sql语句,也会因为不进行语义解析而失效。所以这是一种比较推荐的数据库交互sql语句编写规范。现在很多主流的数据库已经支持预处理,即使不支持,PHP的PDO也会进行预处理模拟实现,这样对于程序员接口一致,不需了解不同数据库对预处理支持的方式差异。

  • 更多PDO细节可以参考官网:http://php.net/manual/zh/pdo.prepared-statements.php。
  • 参数bind的细节可以参考:http://php.net/manual/zh/pdo.constants.php。

 

2、CSRF

完整的攻击过程,可以看这篇前辈的文章:http://www.freebuf.com/articles/web/118352.html。

(1) Low level

  1. <?php
  2. if( isset( $_GET[ 'Change' ] ) ) {
  3.     // Get input  
  4.     $pass_new  = $_GET[ 'password_new' ];
  5.     $pass_conf = $_GET[ 'password_conf' ];
  6.     // Do the passwords match?  
  7.     if$pass_new == $pass_conf ) {
  8.         // They do!  
  9.         $pass_new = mysql_real_escape_string( $pass_new );
  10.         $pass_new = md5( $pass_new );
  11.         // Update the database  
  12.         $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
  13.         $result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' );
  14.         // Feedback for the user  
  15.         echo "<pre>Password Changed.</pre>";
  16.     }
  17.     else {
  18.         // Issue with passwords matching  
  19.         echo "<pre>Passwords did not match.</pre>";
  20.     }
  21.     mysql_close();
  22. }
  23. ?>

所谓的CSRF(Cross-site request forgery)直白的翻译就是跨站点请求伪造。说人话就是攻击者通过诱使victim访问其精心构造的url或者访问其精心构造的页面,来使得攻击者可以以victim的身份做诸如发邮件,发消息,改密码等骚操作。在DVWA这个系列里面,模拟的是修改密码的界面。先来看下low等级的代码,可以说是没有进行仍和的再认证,试下为啥是“再认证”?其实我们在访问到这个修改密码界面的时候,已经登陆过一次,服务器会在每次访问时检查session。所以这是第一道认证。但是这种会话级别的认证对csrf是没有抵抗力的。具体的过程可以参看之前提到的链接。我们可以直接构造url:

http://localhost/dvwa/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change#。让victim访问,或者使用更加隐匿的:

  1. <style>
  2.    form{
  3.     display:none;
  4.    }
  5. </style>

构造无迹表单,结合js发送请求,或者:

  1. <img src="http://192.168.153.130/dvwa/vulnerabilities/csrf/?password_new=hack&password_conf=hack&Change=Change#" border="0"    style="display:none;"/>

来实现欺骗隐匿行踪,达到修改密码的目的。顺便盗用两个别人的poc方便展示:

图片形式诱导

  1. <img src="http://192.168.153.130/dvwa/vulnerabilities/csrf/?password_new=hack&password_conf=hack&Change=Change#" border="0" style="display:none;"/>
  2. <h1>404<h1>
  3. <h2>file not found.<h2>

隐藏表单的形式

  1. <body onload="javascript:csrf()">
  2.   <script>
  3.   function csrf(){
  4.    document.getElementById("button").click();
  5.   }
  6.   </script>
  7.   <style>
  8.   form{
  9.    display:none;
  10.   }
  11.   </style>
  12.        <form action="http://www.dvwa.com/vulnerabilities/csrf/?" method="GET">
  13.            New password:<br />
  14.            <input type="password" AUTOCOMPLETE="off" name="password_new" value="test"><br />
  15.            Confirm new password:<br />
  16.            <input type="password" AUTOCOMPLETE="off" name="password_conf" value="test"><br />
  17.            <br />
  18.            <input type="submit" id="button" name="Change" value="Change" />
  19.        </form>
  20. /body>

 

(2) Medium level

  1. <?php
  2. if( isset( $_GET[ 'Change' ] ) ) {
  3.     // Checks to see where the request came from  
  4.     iferegi$_SERVER[ 'SERVER_NAME' ], $_SERVER[ 'HTTP_REFERER' ] ) ) {
  5.         // Get input  
  6.         $pass_new  = $_GET[ 'password_new' ];
  7.         $pass_conf = $_GET[ 'password_conf' ];
  8.         // Do the passwords match?  
  9.         if$pass_new == $pass_conf ) {
  10.             // They do!  
  11.             $pass_new = mysql_real_escape_string( $pass_new );
  12.             $pass_new = md5( $pass_new );
  13.             // Update the database  
  14.             $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
  15.             $result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' );
  16.             // Feedback for the user  
  17.             echo "<pre>Password Changed.</pre>";
  18.         }
  19.         else {
  20.             // Issue with passwords matching  
  21.             echo "<pre>Passwords did not match.</pre>";
  22.         }
  23.     }
  24.     else {
  25.         // Didn't come from a trusted source  
  26.         echo "<pre>That request didn't look correct.</pre>";
  27.     }
  28.     mysql_close();
  29. }
  30. ?>

在这个级别的CSRF漏洞中,服务端多了一句eregi( $_SERVER[ 'SERVER_NAME' ], $_SERVER[ 'HTTP_REFERER' ]校验,ereg()函数是模式匹配,通过超全局数组获取了请求头referer值(也就是访问者向host发起请求时所在的页面)和host值,并且检查host的值是否在referer中出现。根据权威(https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name),这两个值无法以编程的方式修改,抓包除外,因为在csrf中无法通过抓取客户端的包进行修改,所以按理来说是安全的。实则不然,通过公网服务器,诱使victim访问名字包含host的html文件就可以实现绕过。

 

(3) High level

  1. <?php
  2. if( isset( $_GET[ 'Change' ] ) ) {
  3.     // Check Anti-CSRF token  
  4.     checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
  5.     // Get input  
  6.     $pass_new  = $_GET[ 'password_new' ];
  7.     $pass_conf = $_GET[ 'password_conf' ];
  8.     // Do the passwords match?  
  9.     if$pass_new == $pass_conf ) {
  10.         // They do!  
  11.         $pass_new = mysql_real_escape_string( $pass_new );
  12.         $pass_new = md5( $pass_new );
  13.         // Update the database  
  14.         $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
  15.         $result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' );
  16.         // Feedback for the user  
  17.         echo "<pre>Password Changed.</pre>";
  18.     }
  19.     else {
  20.         // Issue with passwords matching  
  21.         echo "<pre>Passwords did not match.</pre>";
  22.     }
  23.     mysql_close();
  24. }
  25. // Generate Anti-CSRF token  
  26. generateSessionToken();
  27. ?>

在高级别中的代码,主要是使用了Anti-csrf机制,用户每次访问改密页面时,服务器会返回一个随机的token,向服务器发起请求时,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端的请求。我们可以按F12来看看这个token:

关于PHP配置和代码编写不规范造成的安全问题

可以看到不同的用户会返回一个不同的token,这个token在hidden栏里面,这样一来,迫于同源策略,攻击者无法获取victim的token,也就无法实现CSRF攻击。但是真的无法实现吗?配合xss我们还是可以盗取token的,但是这难度无疑增大,我们必须要有服务器的一个xss漏洞来盗取token,然后再使用CSRF。攻击成本也增大。

 

(4) Impossible level

  1. <?php
  2. if( isset( $_GET[ 'Change' ] ) ) {
  3.     // Check Anti-CSRF token  
  4.     checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
  5.     // Get input  
  6.     $pass_curr = $_GET[ 'password_current' ];
  7.     $pass_new  = $_GET[ 'password_new' ];
  8.     $pass_conf = $_GET[ 'password_conf' ];
  9.     // Sanitise current password input  
  10.     $pass_curr = stripslashes$pass_curr );
  11.     $pass_curr = mysql_real_escape_string( $pass_curr );
  12.     $pass_curr = md5( $pass_curr );
  13.     // Check that the current password is correct  
  14.     $data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
  15.     $data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
  16.     $data->bindParam( ':password', $pass_curr, PDO::PARAM_STR );
  17.     $data->execute();
  18.     // Do both new passwords match and does the current password match the user?  
  19.     if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) {
  20.         // It does!  
  21.         $pass_new = stripslashes$pass_new );
  22.         $pass_new = mysql_real_escape_string( $pass_new );
  23.         $pass_new = md5( $pass_new );
  24.         // Update database with new password  
  25.         $data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' );
  26.         $data->bindParam( ':password', $pass_new, PDO::PARAM_STR );
  27.         $data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
  28.         $data->execute();
  29.         // Feedback for the user  
  30.         echo "<pre>Password Changed.</pre>";
  31.     }
  32.     else {
  33.         // Issue with passwords matching  
  34.         echo "<pre>Passwords did not match or current password incorrect.</pre>";
  35.     }
  36. }
  37. // Generate Anti-CSRF token  
  38. generateSessionToken();
  39. ?>

在high的基础上,直接进行了密码再认证,这样一来,即使盗取了token,没有原始密码,也无法进行修改密码的操作,这样CSRF就可以完全避免了。所以通过这个CSRF系列,我们可以知晓,在csrf防御中,采用关键操作的原子性认证,是避免这一漏洞攻击的不二办法。其实我们只关注了CSRF部分,在之前的level中,也还存在了sql注入,在这个impossible版本里,还使用了之前提到的预操纵来进行数据库交互,降低了sql注入的风险。

 

3、Command Injection

(1) Low level

  1. <?php
  2. if( isset( $_POST[ 'Submit' ]  ) ) {
  3.     // Get input  
  4.     $target = $_REQUEST[ 'ip' ];
  5.     // Determine OS and execute the ping command.  
  6.     ifstristr( php_uname( 's' ), 'Windows NT' ) ) {
  7.         // Windows  
  8.         $cmd = shell_exec( 'ping  ' . $target );
  9.     }
  10.     else {
  11.         // *nix  
  12.         $cmd = shell_exec( 'ping  -c 4 ' . $target );
  13.     }
  14.     // Feedback for the end user  
  15.     echo "<pre>{$cmd}</pre>";
  16. }
  17. ?>

过分相信用户的输入,直接拼接到ping 命令中,会造成命令注入。注意到常用的bash命令拼接的方式有||.&&,|,&,;这五个,所以由于没有过滤完全,我们直接进行命令拼接,然后执行任意命令,如127.0.0.1;cat /etc/passwd。

 

(2) Medium level

  1. <?php
  2. if( isset( $_POST[ 'Submit' ]  ) ) {
  3.     // Get input  
  4.     $target = $_REQUEST[ 'ip' ];
  5.     // Set blacklist  
  6.     $substitutions = array(
  7.         '&&' => '',
  8.         ';'  => '',
  9.     );
  10.     // Remove any of the charactars in the array (blacklist).  
  11.     $target = str_replacearray_keys$substitutions ), $substitutions$target );
  12.     // Determine OS and execute the ping command.  
  13.     ifstristr( php_uname( 's' ), 'Windows NT' ) ) {
  14.         // Windows  
  15.         $cmd = shell_exec( 'ping  ' . $target );
  16.     }
  17.     else {
  18.         // *nix  
  19.         $cmd = shell_exec( 'ping  -c 4 ' . $target );
  20.     }
  21.     // Feedback for the end user  
  22.     echo "<pre>{$cmd}</pre>";
  23. }
  24. ?>

这里采用黑名单过滤的方式,注意到黑名单的办法存在的通病就是过滤不完全。可以看到这里也一样,没有把之前提到的东西给过滤完全。其实highlevel也是一样的,过滤语句写得不严谨,多加了空格,造成绕过,这里就不再展开叙述了。同过这个例子可以直观的看到黑名单式过滤方式是不安全的,容易出岔子。接着我们将看到Impossible等级下的白名单试想方式。直接指定只接受num.num.num.num型的输入,也就是我们期望的输入,从而避免了命令执行。

 

(3) Impossible level

  1. <?php
  2. if( isset( $_POST[ 'Submit' ]  ) ) {
  3.     // Check Anti-CSRF token  
  4.     checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
  5.     // Get input  
  6.     $target = $_REQUEST[ 'ip' ];
  7.     $target = stripslashes$target );
  8.     // Split the IP into 4 octects  
  9.     $octet = explode"."$target );
  10.     // Check IF each octet is an integer  
  11.     if( ( is_numeric$octet[0] ) ) && ( is_numeric$octet[1] ) ) && ( is_numeric$octet[2] ) ) && ( is_numeric$octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) {
  12.         // If all 4 octets are int's put the IP back together.  
  13.         $target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];
  14.         // Determine OS and execute the ping command.  
  15.         ifstristr( php_uname( 's' ), 'Windows NT' ) ) {
  16.             // Windows  
  17.             $cmd = shell_exec( 'ping  ' . $target );
  18.         }
  19.         else {
  20.             // *nix  
  21.             $cmd = shell_exec( 'ping  -c 4 ' . $target );
  22.         }
  23.         // Feedback for the end user  
  24.         echo "<pre>{$cmd}</pre>";
  25.     }
  26.     else {
  27.         // Ops. Let the user name theres a mistake  
  28.         echo '<pre>ERROR: You have entered an invalid IP.</pre>';
  29.     }
  30. }
  31. // Generate Anti-CSRF token  
  32. generateSessionToken();
  33. ?>

 

4、Brute Force

暴力枚举攻击,服务端没有在后台设置错误次数上限和相关校验,就会给攻击者暴力枚举用户或者基于字典的密码暴力破解。所以正确的代码编写规范需要规定容许的错误尝试次数,超过这个值就会锁定账户一个定义长的时间。这里需要明确,光加入一个随机的token就想避免Brute Force是相当幼稚的,攻击者通过python脚本来抓取页面的token,就可以完全绕过,这也是high等级所犯的错。这里就不展示,只列出impossible等级和low 等级的代码,供对比阅读:

(1) Low level

  1. <?php
  2. if( isset( $_GET[ 'Login' ] ) ) {
  3.     // Get username  
  4.     $user = $_GET[ 'username' ];
  5.     // Get password  
  6.     $pass = $_GET[ 'password' ];
  7.     $pass = md5( $pass );
  8.     // Check the database  
  9.     $query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
  10.     $result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );
  11.     if$result && mysql_num_rows( $result ) == 1 ) {
  12.         // Get users details  
  13.         $avatar = mysql_result( $result, 0, "avatar" );
  14.         // Login successful  
  15.         echo "<p>Welcome to the password protected area {$user}</p>";
  16.         echo "<img src=\"{$avatar}\" />";
  17.     }
  18.     else {
  19.         // Login failed  
  20.         echo "<pre><br />Username and/or password incorrect.</pre>";
  21.     }
  22.     mysql_close();
  23. }
  24. ?>

 

(2) Impossible level

  1. <?php
  2. if( isset( $_POST[ 'Login' ] ) ) {
  3.     // Check Anti-CSRF token  
  4.     checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
  5.     // Sanitise username input  
  6.     $user = $_POST[ 'username' ];
  7.     $user = stripslashes$user );
  8.     $user = mysql_real_escape_string( $user );
  9.     // Sanitise password input  
  10.     $pass = $_POST[ 'password' ];
  11.     $pass = stripslashes$pass );
  12.     $pass = mysql_real_escape_string( $pass );
  13.     $pass = md5( $pass );
  14.     // Default values  
  15.     $total_failed_login = 3;
  16.     $lockout_time       = 15;
  17.     $account_locked     = false;
  18.     // Check the database (Check user information)  
  19.     $data = $db->prepare( 'SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;' );
  20.     $data->bindParam( ':user', $user, PDO::PARAM_STR );
  21.     $data->execute();
  22.     $row = $data->fetch();
  23.     // Check to see if the user has been locked out.  
  24.     if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) )  {
  25.         // User locked out.  Note, using this method would allow for user enumeration!  
  26.         //echo "<pre><br />This account has been locked due to too many incorrect logins.</pre>";  
  27.         // Calculate when the user would be allowed to login again  
  28.         $last_login = $row[ 'last_login' ];
  29.         $last_login = strtotime$last_login );
  30.         $timeout    = strtotime"{$last_login} +{$lockout_time} minutes" );
  31.         $timenow    = strtotime"now" );
  32.         // Check to see if enough time has passed, if it hasn't locked the account  
  33.         if$timenow > $timeout )
  34.             $account_locked = true;
  35.     }
  36.     // Check the database (if username matches the password)  
  37.     $data = $db->prepare( 'SELECT * FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
  38.     $data->bindParam( ':user', $user, PDO::PARAM_STR);
  39.     $data->bindParam( ':password', $pass, PDO::PARAM_STR );
  40.     $data->execute();
  41.     $row = $data->fetch();
  42.     // If its a valid login...  
  43.     if( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) {
  44.         // Get users details  
  45.         $avatar       = $row[ 'avatar' ];
  46.         $failed_login = $row[ 'failed_login' ];
  47.         $last_login   = $row[ 'last_login' ];
  48.         // Login successful  
  49.         echo "<p>Welcome to the password protected area <em>{$user}</em></p>";
  50.         echo "<img src=\"{$avatar}\" />";
  51.         // Had the account been locked out since last login?  
  52.         if$failed_login >= $total_failed_login ) {
  53.             echo "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>";
  54.             echo "<p>Number of login attempts: <em>{$failed_login}</em>.<br />Last login attempt was at: <em>${last_login}</em>.</p>";
  55.         }
  56.         // Reset bad login count  
  57.         $data = $db->prepare( 'UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;' );
  58.         $data->bindParam( ':user', $user, PDO::PARAM_STR );
  59.         $data->execute();
  60.     }
  61.     else {
  62.         // Login failed  
  63.         sleep( rand( 2, 4 ) );

课后答案张九龄《望月怀远》阅读答案及全诗翻译赏析

望月怀远张九龄海上生明月,天涯共此时。情人怨遥夜,竟夕起相思。灭烛怜光满,披衣觉露滋。不堪盈手赠,还寝梦佳期。注释⑴怀远:怀念远方的亲人。⑵最前面两句:辽阔无边的大海上升起一轮明月,使人想起了远在天涯……
2023-11-22 04:53暂无评论阅读详情

课后答案王安石《次韵唐公三首其三旅思》阅读答案

次韵唐公三首其三旅思王安石此身南北老,愁见问征途。地大蟠三楚,天低入五湖。看云心共远,步月影同孤。慷慨秋风起,悲歌不为鲈②。注:①张壤,字唐公,北宋嘉佑六年契丹国母生辰使,王安石友人。②《晋书&mid……
2023-11-22 04:52暂无评论阅读详情

笔记心得各级干部学习执法为民心得体会

  &ldquo;各级干部都要牢固树立全心全意为人民服务的思想和真心实意对人民负责的精神,做到心里装着群众,凡事想着群众,工作依靠群众,一切为了群众。要坚持权为民所用,情为民所系,利为民所谋,为群众诚……
2023-11-22 04:12暂无评论阅读详情

笔记心得寒假大学生社会实践心得体会

  自从走进了大学,就业问题就似乎总是围绕在我们的身边,成了说不完的话题。在现今社会,招聘会上的大字报都总写着&ldquo;有经验者优先&rdquo;,可还在校园里面的我们这班学子社会经验又会拥有多少……
2023-11-22 04:08暂无评论阅读详情

协议书济南市某美容院转让协议第2篇

&nbsp;&nbsp;__________美容院根据中华人民共和国国务院劳动法规和________市私营企业劳动管理实施办法,结合本美容院经营的具体所需今制订此劳动合同书。&nbsp;&nbsp;双……
2023-11-22 02:36暂无评论阅读详情

剧本劳模宣传短剧剧本《阿咪也想当劳模》

  1、机械厂门卫处,日,外。  清早,机械厂班长李玉伟开着别克赛欧小汽车驶进厂区,门卫室内的保安一边按开电动门,一边朝李玉伟摆手。  李玉伟:(摇下车窗,笑着打招呼)小秦,早。  保安小秦:(笑着)……
2023-11-22 02:11暂无评论阅读详情

教程灰雀说课稿

灰雀说课稿  灰雀说课稿(一):  《灰雀》说课稿  一、说教材  《灰雀》是义务教育课程标准实验教科书,小学语文第五册第二单元的一篇讲读课文。这篇课文记叙了列宁在莫斯科郊外养病期间爱护灰雀的故事。列……
2023-11-22 00:41暂无评论阅读详情

课件“吴隐之字处默,濮阳鄄城人”阅读答案及原文

吴隐之字处默,濮阳鄄城人。美姿容,善谈论,博涉文史,以儒雅标名。弱冠而介立,有清操,虽儋石无储,不取非其道。事母孝谨,及其执丧,哀毁过礼。与太常韩康伯邻居,康伯母,贤明妇人也,每闻隐之哭声,辍餐投箸,……
2023-11-22 00:38暂无评论阅读详情

标签