继续把dvwa环境安全级别调整为impossible
观察界面
看上去似乎和low级别没有大的区别,只是浏览器中get方法提交的参数多了一个。
impossible.php代码如下
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$id = $_GET[ 'id' ];
// Was a number entered?
if(is_numeric( $id )) {
// Check the database
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
$row = $data->fetch();
// Make sure only 1 result is returned
if( $data->rowCount() == 1 ) {
// Get values
$first = $row[ 'first_name' ];
$last = $row[ 'last_name' ];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
在impossible级别这个界面上如果尝试注入是不会成功的。因为这里增加了2个安全防范措施。
1.Check Anti-CSRF token
在impossible.php中有以下代码,是为了防止CSRF攻击。可以参考《Web安全之CSRF攻击》
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
CSRF,Cross Site Request Forgery,中文是跨站点请求伪造。CSRF攻击者在用户已经登录目标网站之后,诱使用户访问一个攻击页面,利用目标网站对用户的信任,以用户身份在攻击页面对目标网站发起伪造用户操作的请求,达到攻击目的。
CSRF攻击是源于Web的隐式身份验证机制!Web的身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的。
CSRF攻击的一般是由服务端解决。
应对CSRF的方法有
- 尽量使用POST,限制GET
- 浏览器Cookie策略
- 加验证码
- Referer Check ,例如:防止图片盗链
- Anti CSRF Token
在上面代码中就是采用的最后一种。
那么什么是Token呢?
Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。这样可以减轻服务器的压力,减少频繁的查询数据库。
使用Token有2种方式:
- 用设备号/设备mac地址作为Token
- 用session值作为Token用session值作为Token
Token的好处是服务端不需要存储相应信息。
大概的流程是这样的:
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
- 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
- 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据
当然,如果Token被不怀好意的人从中间获取到该信息时,也容易被利用,非法获取数据。 想增强安全性,一般可以在服务端生成时配合时间戳,服务端在接收到client发来带token的信息时,先检测token的时间戳信息,如果该时间戳在超过某个时间点时,就认为过期,需要重新获取。
Token一般用在两个地方:
- 防止表单重复提交:服务器端第一次验证相同过后,会将session中的Token值更新。若用户重复提交,第二次的验证判断将失败,因为用户提交的表单中的Token没变,但服务器端session中Token已经改变了。
- anti csrf攻击(跨站点请求伪造):服务器端会对Token值进行验证,判断是否和session中的Token值相等。若相等,则可以证明请求有效,不是伪造的。
现在我们来看看代码里如何生成的。
在登录界面/var/www/html/login.php和本页面都能生成Token
**// Anti-CSRF
generateSessionToken();
这个函数在/var/www/html/dvwa/includes/dvwaPhpIds.inc.php中
function generateSessionToken() { # Generate a brand new (CSRF) token
if( isset( $_SESSION[ 'session_token' ] ) ) {
destroySessionToken();
}
$_SESSION[ 'session_token' ] = md5( uniqid() );
}
验证就是在impossible.php中。
请注意:要避免"加token但不进行校验"的情况。在session中增加了token,但服务端没有对token进行验证,那根本起不到防范的作用。
我们可以尝试提交2次
http://192.168.99.100/vulnerabilities/sqli/index.php?id=1&Submit=Submit&user_token=9f874c66039244204e9951d4c7f7d551#
http://192.168.99.100/vulnerabilities/sqli/index.php?id=1&Submit=Submit&user_token=76f150ddea1eb5a7e670718f1f9f4d61#
可以发现浏览器的URL中2次的Token是不一样的。
2.sql预编译语句
观察以下代码,这个就是预编译。
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程。预编译语句能防止sql注入。
mysql4.1以后支持预编译。
在impossible.php中还限制了只允许返回一条数据。
if( $data->rowCount() == 1 )