0x00 暴力破解
暴力破解,是指黑客利用密码字典,使用穷举法猜解出用户口令,是现在最为广泛使用的攻击手法之一。如2014年轰动全国的12306撞库事件,实质就是暴力破解攻击。
C/S架构
Hydra
-
破解MySQL
hydra -L user.txt -P pass.txt 192.168.0.1 mysql
-
破解FTP
hydra -l admin -P pass.txt -t 5 192.168.0.1 ftp
-
破解ssh
hydra -L user.txt -P pass.txt -e -n -t 5 -vV 192.168.0.1 ssh
-
破解rdp
hydra -l administrator -P pass.txt www.xxx.com rdp -V
-
破解pop3
hydra -l root -P pass.txt my.pop3.mail pop3
Medusa
特性:
- 基于线程的并行测试,可同时测试多台主机
- 模块化设计,每个服务模块作为一个独立的mod文件
- 可以灵活输入目标主机信息和测试指标
破解示例:
-
破解smbnt
medusa -h www.xxx.com -u administrator -P pass.txt -e -ns -M smbnt
-
破解MySQL
medusa -h 192.168.0.1 -u root -P pass.txt -M mysql
-
破解MSSQL
medusa -h 192.168.0.1 -u sa -P pass.txt -t 5 -f -M mssql
-
破解SSH
medusa -M ssh -H host.txt -U user.txt -P password
B/S架构
Burp suite
抓包send to intruder,寻找标志位、配置参数,start attack
防止暴力破解
-
密码复杂性
1.对重要的应用系统,密码长度最低为6位以上
2.不能以关键特征为密码
3.用户名与密码之间不能有任何联系
4.客户端自定义密码复杂性方案
-
验证码措施
使用验证码可以有效防止恶意破解密码、刷票、灌水等恶意行为。但验证码同样也会带来安全问题:
1.没有及时销毁session,导致验证码复用
2.没有进行session中验证码的非空判断
3.session中验证码数量有限,导致暴力绕过
4.验证码不够模糊,导致OCR识别
-
限制登录错误次数
当用户登陆时,不是直接登录,而是先在登录日志中查找用户登录错误的次数、时间等信息。如果操作连续错误、失败,那么将采取一定措施限制登录
从代码层杜绝暴力破解
<?php
if( isset( $_POST[ 'Login' ] ) && isset ($_POST['username']) && isset ($_POST['password']) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Sanitise username input
$user = $_POST[ 'username' ];
$user = stripslashes( $user );
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitise password input
$pass = $_POST[ 'password' ];
$pass = stripslashes( $pass );
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );
// Default values
$total_failed_login = 3;
$lockout_time = 15;
$account_locked = false;
// Check the database (Check user information)
$data = $db->prepare( 'SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
$row = $data->fetch();
// Check to see if the user has been locked out.
if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) ) {
// User locked out. Note, using this method would allow for user enumeration!
//echo "<pre><br />This account has been locked due to too many incorrect logins.</pre>";
// Calculate when the user would be allowed to login again
$last_login = strtotime( $row[ 'last_login' ] );
$timeout = $last_login + ($lockout_time * 60);
$timenow = time();
/*
print "The last login was: " . date ("h:i:s", $last_login) . "<br />";
print "The timenow is: " . date ("h:i:s", $timenow) . "<br />";
print "The timeout is: " . date ("h:i:s", $timeout) . "<br />";
*/
// Check to see if enough time has passed, if it hasn't locked the account
if( $timenow < $timeout ) {
$account_locked = true;
// print "The account is locked<br />";
}
}
// Check the database (if username matches the password)
$data = $db->prepare( 'SELECT * FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR);
$data->bindParam( ':password', $pass, PDO::PARAM_STR );
$data->execute();
$row = $data->fetch();
// If its a valid login...
if( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) {
// Get users details
$avatar = $row[ 'avatar' ];
$failed_login = $row[ 'failed_login' ];
$last_login = $row[ 'last_login' ];
// Login successful
echo "<p>Welcome to the password protected area <em>{$user}</em></p>";
echo "<img src=\"{$avatar}\" />";
// Had the account been locked out since last login?
if( $failed_login >= $total_failed_login ) {
echo "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>";
echo "<p>Number of login attempts: <em>{$failed_login}</em>.<br />Last login attempt was at: <em>${last_login}</em>.</p>";
}
// Reset bad login count
$data = $db->prepare( 'UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
} else {
// Login failed
sleep( rand( 2, 4 ) );
// Give the user some feedback
echo "<pre><br />Username and/or password incorrect.<br /><br/>Alternative, the account has been locked because of too many failed logins.<br />If this is the case, <em>please try again in {$lockout_time} minutes</em>.</pre>";
// Update bad login count
$data = $db->prepare( 'UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
}
// Set the last login time
$data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
- 加入Anti-CSRF token机制,在提交登录请求时,先检查user_token,防止无脑破解
- 对登录失败次数做限制,当密码错误达到一定次数时,将账户锁定一段事件
- 采用PDO机制防御登录框sql注入
0x01 SQL注入
SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
注入分类
- 数字型
- 字符型
数据库分类
- Access
- MsSQL
- MySQL
- Oracle
- Postgresql
提交方式
- GET
- POST
- Cookie
显错注入
-
updatexml
select * from message where id = 1 and updatexml(1,(concat(0x7C,(select @@version))),1);
-
extractvalue
select * from message where id =1 and extractvalue(1,concat(0x7C,(select user())));
-
floor
select * from message where id =1 union select * from (select count(*),concat(floor(rand(0)*2),(select user()))a from information_schema.tables group by a)b
延时注入
select * from users where id = 1 and sleep(3)
- MySQL:sleep()
- SQL Server:waitfor delay()
- Oracle:DBMS_LOCK.SLEEP
宽字节注入
魔术引号:magic_quotes_gpc选项开启时,单引号、双引号、反斜线、空字符都会被自动加上一个反斜线转义。注入类型是字符型时无法构成注入。
可以通过宽字节突破PHP的转义,继续闭合SQL语句进行注入。
堆叠注入
在’ ; '结束一个sql语句后继续构造下一条语句,造成堆叠注入。
导入导出相关操作
- load_file():导出文件
- LOAD DATE INFILE:文件导入数据库
- select * INTO OUTFILE ‘file_name’:导入到文件
防止SQL注入
- 严格的数据类型
- 特殊字符转义
- 使用预编译语句
- 框架技术
相关安全函数
- addslashes(string):在预定义字符前添加反斜线(单引号、双引号、反斜线、空字符)
- stripslashes():删除由addslashes函数添加的反斜线
- mysql_real_escape_string():转义sql语句中使用的字符串中的特殊字符(’\x00’、’\n’、’\r’、’’、’ ’ ‘、’ " ‘、’\x1a’)
从代码层杜绝SQL注入
<?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();
// Get results
if( $data->rowCount() == 1 ) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
- 屏蔽错误信息,提高注入难度
- 加入Anti-CSRF token机制,提高安全性
- 限制输出信息为1,防止拖库
- 采用PDO技术,通过编译SQL语句模板来运行sql语句的机制 ,划清代码与数据的界限
0x02 命令执行
命令执行漏洞即黑客可以直接在Web应用中执行系统命令,从而获取敏感信息或者拿下shell权限。
执行条件
- 是否调用系统命令
- 函数或参数是否可控
- 是否拼接注入命令
命令执行函数
- system()
- shell_exec()
- exec()
- passthru()
- eval()
- callback
框架执行
- MVC架构
- Struts2框架执行
- Think PHP框架执行
相关安全函数
- Escapeshellarg函数处理相关参数
- safe_mode_exec_dir执行可执行的文件路径
反序列化导致命令执行
攻击者通过unserialize()控制_destruct()
或_wakeup()
中函数的输入,攻击payload可以先模仿目标代码的实现过程,然后再通过调用serialize()获得。
防止命令执行
- 尽量不使用系统执行命令
- 在进入执行命令函数/方法前,变量做好过滤,敏感字符转义
- 在使用动态函数前,确保使用的函数是指定的函数之一
- 不能完全控制的危险函数最好不要使用
从代码层杜绝命令执行漏洞
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$target = $_REQUEST[ 'ip' ];
$target = stripslashes( $target );
// Split the IP into 4 octects
$octet = explode( ".", $target );
// Check IF each octet is an integer
if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) {
// If all 4 octets are int's put the IP back together.
$target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];
// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// Feedback for the end user
echo "<pre>{$cmd}</pre>";
}
else {
// Ops. Let the user name theres a mistake
echo '<pre>ERROR: You have entered an invalid IP.</pre>';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
- Anti-CSRF token
- explode函数将字符串打散为数组
- 对参数进行严格限制
0x03 文件上传
文件上传漏洞是指由于程序员在对用户文件上传部分的控制不足或者处理缺陷,而导致的用户可以越过其本身权限向服务器上上传可执行的动态脚本文件。
解析漏洞
-
IIS解析漏洞
1.建立.asa、.asp格式的文件夹,其目录下文件都将被当作asp文件解析。
2.文件名为*.asp;1.jpg,IIS6.0会以asp脚本执行
-
Apache解析漏洞
Apache1.x和2.x遇到不认识扩展名时,会从后往前解析
-
PHP CGI解析漏洞
cgi.fi:x_pathinfo开启,当访问不存在的文件时,php会向前递归解析
绕过上传
- 客户端检测
- 服务器端检测
文本编辑器上传
- 敏感信息
- 黑名单策略错误
- 二次上传
相关安全函数
- getimagesize():用于获取图像大小及相关信息,成功返回一个数组
- imagecreatefromjpeg():返回图片文件的图像标识
- imagejpeg(image,filename,quality):从image图像以filename为文件名创建一个JPEG图像
- imagedestroy():销毁图像资源
防止上传漏洞
- 目录限制
- 白名单机制
- 文件重命名
- 限制上传覆盖.htaccess
- 服务器、web容器合理搭配
从代码层杜绝上传漏洞
<?php
if( isset( $_POST[ 'Upload' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';
//$target_file = basename( $uploaded_name, '.' . $uploaded_ext ) . '-';
$target_file = md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
$temp_file = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) );
$temp_file .= DIRECTORY_SEPARATOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
// Is it an image?
if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) &&
( $uploaded_size < 100000 ) &&
( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) &&
getimagesize( $uploaded_tmp ) ) {
// Strip any metadata, by re-encoding image (Note, using php-Imagick is recommended over php-GD)
if( $uploaded_type == 'image/jpeg' ) {
$img = imagecreatefromjpeg( $uploaded_tmp );
imagejpeg( $img, $temp_file, 100);
}
else {
$img = imagecreatefrompng( $uploaded_tmp );
imagepng( $img, $temp_file, 9);
}
imagedestroy( $img );
// Can we move the file to the web root from the temp folder?
if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
// Yes!
echo "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
}
else {
// No
echo '<pre>Your image was not uploaded.</pre>';
}
// Delete any temp files
if( file_exists( $temp_file ) )
unlink( $temp_file );
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
- Anti-CSRF token
- substr截取,文件名随机数重命名、后缀名白名单校验
- MIME类型验证
- 检查文件内容
- 重新生成图片
0x04 文件包含
程序开发人员通常会把可重复使用的函数写到单个文件中,在使用某些函数时,直接调用此文件而无需再次编写,这种调用文件的过程被称为包含。被包含的文件设置为变量,用来动态调用时,导致客户端可以调用一个恶意文件,造成文件包含漏洞。
包含类型
- 本地包含
- 远程包含
文件包含函数
- include()
- require()
- include_once()
- require_once()
文件包含利用
- 读取敏感文件
- 远程包含shell
- 本地包含配合文件上传
- php封装协议
- 写入php文件
- 包含Apache日志文件
- 截断包含
防止包含漏洞
- 严格判断包含中的参数是否外部可控
- 限制被包含的文件只能在某一文件夹中,限制目录跳转字符
- 验证被包含的文件是否在白名单中
- 尽量不使用动态包含
从代码层杜绝包含漏洞
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Only allow include.php or file{1..3}.php
if( $file != "include.php" && $file != "file1.php" && $file != "file2.php" && $file != "file3.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}
?>
白名单机制,只允许包含白名单中的文件。
0x05 XSS
XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。
攻击类型
- 反射型XSS
- 存储型XSS
- DOM型XSS
XSS高级利用
- 盗取用户Cookie
- 修改网页内容
- 网站挂马
- 网站重定向
- XSS蠕虫
XSS Framework
- BeEF
- XSS Platform
相关安全函数
- htmlspecialchars()
- htmlentities()
将预定义字符转化为HTML实体(&、"、’、<、>)
防止XSS跨站
-
输入与输出
1.标签内输出
2.属性内输出
3.事件中输出
4.CSS中输出
5.Script标签中输出
-
HttpOnly
浏览器向服务器发送请求,服务器返回时发送Set-Cookie头向客户端浏览器写入Cookie,HttpOnly则是在这个时候标记的,以解决XSS后的Cookie劫持攻击。
从代码层杜绝XSS跨站
Reflected:
<?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$name = htmlspecialchars( $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
// Generate Anti-CSRF token
generateSessionToken();
?>
Stored:
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = stripslashes( $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$name = htmlspecialchars( $name );
// Update database
$data = $db->prepare( 'INSERT INTO guestbook ( comment, name ) VALUES ( :message, :name );' );
$data->bindParam( ':message', $message, PDO::PARAM_STR );
$data->bindParam( ':name', $name, PDO::PARAM_STR );
$data->execute();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
- Anti-CSRFtoken
- htmlspecialchars()
- PDO机制
0x06 CSRF
跨站请求伪造,也被称为one-click attack或者session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
攻击场景
- GET
- POST
Cookie机制
- 持久型Cookie
- Session Cookie
同源策略
同源策略是浏览器的一个安全限制,从一个源加载的文档或者脚本默认不能访问另一个源的资源。例如a.com/111/html页面不能访问b.com/person这种接口,因为他们是不同的源。
跨域威胁
- JSONP跨域
- CORS跨域
- PostMessage跨域
防御CSRF
- 二次确认
- 验证码机制
- token验证
- 检查Referer
从代码层杜绝CSRF
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$pass_curr = $_GET[ 'password_current' ];
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Sanitise current password input
$pass_curr = stripslashes( $pass_curr );
$pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_curr = md5( $pass_curr );
// Check that the current password is correct
$data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->bindParam( ':password', $pass_curr, PDO::PARAM_STR );
$data->execute();
// Do both new passwords match and does the current password match the user?
if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) {
// It does!
$pass_new = stripslashes( $pass_new );
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update database with new password
$data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' );
$data->bindParam( ':password', $pass_new, PDO::PARAM_STR );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->execute();
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match or current password incorrect.</pre>";
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
- Anti-CSRF token
- 用户确认(二次确认、输入原密码等)
- PDO技术