言
今天给各位介绍下Tengine/Nginx服务器绑定多个域名多个网站的配置方法。
Tengine/Nginx配置文件
[root@localhost ~]# whereis nginx nginx: /usr/local/nginx [root@localhost ~]#
Nginx的配置文件
vim /usr/local/nginx/conf/nginx.conf #打开nginx配置文件命令 你也可以使用你喜欢的编辑工具如vi emacs gedit
原文件
#以下为Nginx 配置文件nginx.conf默认内容,已加注解。 user www www-data; #以www会员和www-data会员组运行nginx worker_processes 1; #最大进程数,一般设为cpu核心数量 如你是4核cpu 可以设为4 #error_log logs/error.log; #指定错误日志文件路径,默认当前配置文件的父级目录logs下的error.log #error_log logs/error.log notice; #指定错误日志文件路径并指定为只记录notice级别错误 #error_log logs/error.log info; #指定错误日志文件路径并指定为只记录info级别错误 #pid logs/nginx.pid; ##记录nginx运行时的进程ID events { worker_connections 1024; #允许的最大连接数即tcp连接数 } # load modules compiled as Dynamic Shared Object (DSO) # 动态模块加载(DSO)支持。加入一个模块不再需要重新编译整个Tengine 这个是Tengine特有的 #dso { # load ngx_http_fastcgi_module.so; #fastcgi模块 # load ngx_http_rewrite_module.so; #URL重写模块 #} http { include mime.types; #设定mime类型,类型由conf目录下mime.type文件定义 default_type application/octet-stream; #默认为 任意的二进制数据 ## 可配置日志格式: $remote_addr访客ip ## $remote_user已经经过Auth Basic Module验证的用户名 ## $time_local访问时间 ## $request请求的url ## $body_bytes_sent 传送页面的字节数 $http_referer访问来源 #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; #访问记录日志 sendfile on; #开启高效文件传输模式 注意:如果图片显示不正常把这个改成off。 #tcp_nopush on; #防止网络阻塞 #keepalive_timeout 0; keepalive_timeout 65; #长连接超时时间,单位是秒 #gzip on; #开启gzip压缩 server {#虚拟主机的配置 listen 80; #监听80端口 server_name localhost; #绑定域名可以有多个,用空格隔开 #charset koi8-r; #字符编码 可设为 utf-8 #access_log logs/host.access.log main; #访问记录日志 location / { ##网站根目录设置在这里 root html; #配置文件父级html目录,可以设到其它目录如/home/www目录,注意目录的所有者和权限 本文开头处user的信息 index index.html index.htm; #默认索引文件,从左到右,如:index.php index.html index.htm 空格分开 } #error_page 404 /404.html; #指定404错误文件位置 root指定目录下的404.html 以下50x文件同理 # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; ##服务器50x得的错误都跳转到html/50x.html文件 location = /50x.html { root html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # 配置处理php文件,需要安装PHP #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; ##禁止使用.htaccess文件 #} } # #server { # listen 8000; # listen somename:8080; # server_name somename alias another.alias; # location / { # root html; # index index.html index.htm; # } #} # HTTPS server # #server { # listen 443 ssl; # server_name localhost; # ssl_certificate cert.pem; # ssl_certificate_key cert.key; # ssl_session_cache shared:SSL:1m; # ssl_session_timeout 5m; # ssl_ciphers HIGH:!aNULL:!MD5; # ssl_prefer_server_ciphers on; # location / { # root html; # index index.html index.htm; # } #} }
修改后
###以下为修改过的nginx.conf配置文件,已加注解。 user www www-data; #以www会员和www-data会员组运行nginx worker_processes 1; #最大进程数,一般设为cpu核心数量 如你是4核cpu 可以设为4 error_log logs/error.log; #指定错误日志文件路径,默认当前配置文件的父级目录logs下的error.log #error_log logs/error.log notice; #指定错误日志文件路径并指定为只记录notice级别错误 #error_log logs/error.log info; #指定错误日志文件路径并指定为只记录info级别错误 pid logs/nginx.pid; ##记录nginx运行时的进程ID events { use epoll; #新加 提高nginx的性能,限Linux下使用 worker_connections 1024; #允许的最大连接数即tcp连接数 } # load modules compiled as Dynamic Shared Object (DSO) # 动态模块加载(DSO)支持。加入一个模块不再需要重新编译整个Tengine 这个是Tengine特有的 #dso { # load ngx_http_fastcgi_module.so; #fastcgi模块 # load ngx_http_rewrite_module.so; #URL重写模块 #} http { include mime.types; #设定mime类型,类型由conf目录下mime.type文件定义 default_type application/octet-stream; #默认为 任意的二进制数据 ##可配置日志格式: $remote_addr访客ip ## $remote_user已经经过Auth Basic Module验证的用户名 ## $time_local访问时间 ## $request请求的url ## $body_bytes_sent 传送页面的字节数 ## $http_referer访问来源 #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; #访问记录日志 sendfile on; #开启高效文件传输模式 注意:如果图片显示不正常把这个改成off。 #tcp_nopush on; #防止网络阻塞 #keepalive_timeout 0; keepalive_timeout 65; #长连接超时时间,单位是秒 gzip on; #开启gzip压缩 ## 新加 include 项引入/usr/local/nginx/vhosts/目录下所有.conf结尾的虚拟机配置文件 include /usr/local/nginx/vhosts/*.conf }
建立虚拟机配置目录
mkdir /usr/local/nginx/vhosts/
新建网站配置文件
vim /usr/local/nginx/vhosts/test.conf ##www.test.my 的配置文件 server { #虚拟主机的配置 listen 80; #监听80端口 server_name www.test.my www.test.my; #绑定域名可以有多个,用空格隔开 #charset koi8-r; #字符编码 可设为 utf-8 charset utf-8; #access_log logs/host.access.log main; #访问记录日志 location / { ##网站根目录设置在这里 root html; #配置文件父级html目录,可以设到其它目录如/home/www目录,注意目录的所有者和权限 本文开头处user的信息 index index.html index.htm; #默认索引文件,从左到右,如:index.php index.html index.htm 空格分开 } #error_page 404 /404.html; #指定404错误文件位置 root指定目录下的404.html 以下50x文件同理 # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; ##服务器50x得的错误都跳转到html/50x.html文件 location = /50x.html { root html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # 配置处理php文件,需要安装PHP #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; ##禁止使用.htaccess文件 #} }
配置多个网站
#如再配置一个域名为 yuntheme.com站点的配置文件 vim /usr/local/nginx/vhosts/yuntheme.conf #复制刚才的test.conf的内容,只要像下面修改就行了 location / { ##网站根目录设置在这里 root /usr/local/nginx/html/yuntheme; #修改此处 index index.html index.htm; }
完成配置,重启Nginx
累点滴,汇成江海。咱们从最最基础的PHP知识开始学习,一步一个脚印的开启PHP的学习旅途吧。
请点击右上角“关注”按钮关注我们哟:跟着木辛老师学习PHP编程知识,变身快乐的编程达人吧~
同学们好呀!木辛老师又来了。
咱们在开始PHP的学习之前,需要先准备一个可以提供PHP服务的Web服务器。我们就复用木辛老师专栏中的一个教程,使用Homestead本地开发环境进行学习呗。
传送门:《Laravel第一课:搭建Laravel开发环境》
也可以关注木辛老师的Laravel专栏哟:
大家配置好本地开发环境以后,还需要稍微设置一下,针对这个项目,在Homestead配置文件中作如下设置,
添加指向当前项目根目录的配置:
sites: - map: learning_php.test to: /Code/zyoo/learning_php to: /Code/zyoo/learning_php
另外还需要在本机hosts文件中添加一个域名指向:
sudo vim /etc/hosts
并添加如下记录:
192.168.10.10 learning_php.test
最后,添加一个测试文件:
php代码
然后,打开浏览器,访问域名查看页面结果
执行效果
大家可以看到,我们已经可以成功的访问到测试用的PHP文件了。
好了,万事俱备,只需要学习了。那么,咱们开始吧~
几乎绝大部分服务器端的脚本语言最初设计的应用场景之一就是处理HTML表单。木辛老师要翻出家底,将自己最心爱的在线图书商城,用来作为学习PHP基础知识的场景吧。
通过这个表单页面,我们可以知道顾客订购的商品,订单的金额以及其他一些附属信息。HTML代码请看下方:
<html> <head> <title>木辛老师的PHP基础入门教程</title> </head> <body> <form action="processorder.php" method="POST"> <table style="border: 0px;"> <tr style="background: #cccccc"> <td style="width: 150px;text-align:center;">图书名称</td> <td style="width: 50px;text-align:center;">数量</td> </tr> <tr> <td>PHP入门指南</td> <td><input type="text" name=“book_name_01" size="3" maxlength="3"/></td> </tr> <tr> <td>PHP和MySQL开发</td> <td><input type="text" name="book_name_02" size="3" maxlength="3"/></td> </tr> <tr> <td>Laravel入门</td> <td><input type="text" name="book_name_03" size="3" maxlength="3"/></td> </tr> <tr> <td colspan="2" style="text-align: center;"> <input type="submit" value="提交订单"/> </td> </tr> </table> </form> </body> </html>
咱么直接通过浏览器访问这个HTML页面,看看效果:
页面显示
哈,简单的页面,我们已经开启Web开发神秘旅程了。继续加油!
大家可能注意到了一个细节:在html代码的form表单部分,action属性我们指向了一个php脚本:
<form action="processorder.php" method="POST”>
具体的PHP脚本的学习我们很快就能看到。这里只是稍微提一下,这个action属性值就是用户点击“提交订单”按钮时将要请求的URL。
用户在表单中输入的数据,会以POST的方式,发送给URL指向的PHP文件进行处理。
那如何处理这个表单呢?又如何让PHP代码起作用的?
要处理这个表单,我们需要创建一个php文件,它的名字需要和form中action属性的值保持一致。
那么,我们就创建一个名字叫做processorder.php的文件吧。
代码可以先这么写,看看是否能起作用哈:
<html> <head> <title>订单处理结果</title> </head> <body> <h1> 木辛老师的在线图书馆</h1> <h2> 订单处理结果通知</h2> <?php echo '<p>订单已处理完成</p>'; // 这里是PHP的代码 ?> </body> </html>
保持文件,并刷新页面。这个时候我们点击“提交订单”按钮,效果如下:
php执行结果
大家可以看到,红框部分就是通过PHP代码输出的结果。这样,我们就实现了通过Web方式执行了PHP代码的需求,这么一看PHP还是非常简单的吧。
我们顺便在看看这个页面的源代码吧,看一下PHP代码如何在HTML页面中完成任务的吧:
源代码
通过页面源码,我们发现刚才写的PHP代码已经不见了,取而代之的是
<p>订单已处理完成</p>
这是怎么回事呢?
这是因为PHP解释器在脚本运行的时候,将该脚本的输出替代了脚本自身的代码,通过这种方式,就可以生成可以在任何浏览器上运行的HTML页面了。也就是说,浏览器是不需要学会PHP的。
通过这段代码,我们可以学习一些PHP的基础知识:
第一种情况:在HTML中混写PHP和HTML代码,需要为php添加标记。PHP代码会以“<?php”作为开始,以“?>”作为结束。这些符号就叫做PHP标记,它们主要用来告诉服务器PHP代码的开始和截止,在这两个起止符号之间的任何代码,服务器都会以PHP语法来解析。
另一种情况:之后,我们写纯PHP的时候,每个文件也需要添加PHP标记。不过呢,结束标记可以省略,这也是很大一部分PHPer默认遵守的规则。
在PHP的开始和截止标记之间,就是PHP语句了,通过这些内容可以告诉PHP解释器应该进行如何的操作,在我们这个例子里,通过:
echo '<p>订单已处理完成</p>’;
使用echo语句完成了一个非常简单的操作,仅是将echo后边的字符串原样打印到浏览器中。这里需要特别注意的一点就是每个PHP语句后边都需要添加英文的分号作为语句的结束符,否则会出现错误,但是在这个html页面中,因为只有一句代码,忽略掉分号也是不会报错的。
但是还是强烈建议大家养成习惯:每句PHP代码结束都要以分号结尾哟!
一般情况下,为了让代码更加清晰和整洁,在编码的过程中会添加一些空格,这些空格包括:回车换行、空格、制表符等都被认为是空格。
当然了,浏览器并不会在意你是否输入了空格,同样的PHP服务器端解析器也会忽略这些,这些空格仅是给编写代码的人看的。
但是,木辛老师还是再次强烈建议,在代码的适当位置添加空格或者空行,这样做可以很有效的提升代码的可阅读性,方便后期的维护工作。
最后在讲讲注释,理论上在编程中出现频率非常高的一个知识点。
为什么说理论上呢,因为这么重要的一个要点,在实际开发中很容易被广大开发者忽略呢!
由于种种原因吧,开发者很不习惯在开发过程中写非常详尽的注释,而且有时候在Git提交时也是草草的一笔带过。这样做的后果就是,若干时间后,当你再次拿到这段代码,可能会花费更多的时间梳理它。
所以,善于写注释,也是提高生产效率的一种有效手段。
PHP解释器同样会在执行的时候忽略掉注释,也就是说就好比像空格一样,PHP解析器会跳过注释,它只负责执行PHP代码!
PHP脚本中的注释比较丰富,有很多类似C语言的风格,比如:
多行注释:
/* 这是 一个 多行 注释 /*
可以看出来,多行注释以 /*开始,以*/结束。同样的和C语言是一样的,多行注释是不能嵌套的。
当然了,除了多行注释之外,也支持单行注释:
echo '<p>订单已处理完成</p>'; // 这里是PHP的代码
或者这种:
echo '<p>订单已处理完成</p>’; #这里是PHP的代码
不论采取哪种风格的注释,在注释符号之后的所有内容,PHP解释器都会认识不需要处理的,这一点一定要注意呀!
好了,今天的课程咱就先讲到这里。
小朋友们不要忘记关注我们哟 ,下期课程更精彩,请大家一起期待吧~
快乐编程,快乐成长,拜拜!
习网络攻防技术一定离不开靶场练习,Dvwa是一个非常经典的靶场,涵盖csrf、sql注入、文件包含等漏洞环境,并有Low、Medium、High、Impossible四种不同的安全等级,适合新手练习,通过该靶场可以由浅入深的学习漏洞原理和代码审计。
本文是i春秋论坛版主「Adian大蝈蝈」表哥直接在Dvwa high进行测试的完整攻略,对靶场练习是一个非常好的指导,感兴趣的小伙伴快来学习吧。
DVWA共有14个漏洞选项,我们逐一来看:
Brute Force
我们先来看看high.php
<?phpif( isset( $_GET[ 'Login' ] ) ) { // Check Anti-CSRF token checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // Sanitise username input $user = $_GET[ '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 = $_GET[ '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 ); // Check database $query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); if( $result && mysqli_num_rows( $result ) == 1 ) { // Get users details $row = mysqli_fetch_assoc( $result ); $avatar = $row["avatar"]; // Login successful $html .= "<p>Welcome to the password protected area {$user}</p>"; $html .= "<img src=\"{$avatar}\" />"; } else { // Login failed sleep( rand( 0, 3 ) ); $html .= "<pre><br />Username and/or password incorrect.</pre>"; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);}// Generate Anti-CSRF tokengenerateSessionToken();?>
High级别的猜解加了一个防止CSRF的验证token,使用了stripslashes( )等函数来转义或过滤,虽然加大了猜解的难度,但是还是可以猜解的。
我们先正常抓包:
得到完整数据包之后,我们把需要猜解的参数范围选中user_token和password,选择Pitchfork测试类型。
找到Redirections选中always允许重定向:
最后在Options中找到Grep-Extract模块,点击Add,并设置筛选条件,得到user_token。
然后设置payload,带token参数的paylaod直接把token粘贴进去就可以了,其他照常。
然后开始猜解,关于其他文章提到的线程设置为1,新版本的burpsuite设置了Pitchfork之后,就默认为1不可更改,所以这个问题不再叙述了。
Command Injection(命令执行)
我们先简单的试一下:
可见$被过滤了,在看一下代码:
<?phpif( isset( $_POST[ 'Submit' ] ) ) { // Get input $target = trim($_REQUEST[ 'ip' ]); // Set blacklist $substitutions = array( '&' => '', ';' => '', '| ' => '', '-' => '', '$' => '', '(' => '', ')' => '', '`' => '', '||' => '', ); // Remove any of the charactars in the array (blacklist). $target = str_replace( array_keys( $substitutions ), $substitutions, $target ); // 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 $html .= "<pre>{$cmd}</pre>";}?>
代码可以看见将$;()都进行了转换,转成了空字符串,这也就导致了我们输入的这些能同时执行其他命令的符号都无法使用了。
但是仔细看过滤 "| ",如果我们把后面的空格删去直接执行,也是可以执行的。
CSRF
还是看源代码high.php
<?phpif( isset( $_GET[ 'Change' ] ) ) { // Check Anti-CSRF token checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // Get input $pass_new = $_GET[ 'password_new' ]; $pass_conf = $_GET[ 'password_conf' ]; // Do the passwords match? if( $pass_new == $pass_conf ) { // They do! $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 the database $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); // Feedback for the user $html .= "<pre>Password Changed.</pre>"; } else { // Issue with passwords matching $html .= "<pre>Passwords did not match.</pre>"; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);}// Generate Anti-CSRF tokengenerateSessionToken();?>
由上面的代码可见,High级别的代码加入了Anti-CSRF token机制,用户每次访问改密码时,服务器会返回一个随机的token,提交的参数带有正确的token才能执行,我们可以利用burp的插件CSRFTokenTracker绕过token验证,这里我借用一下其他人的图片。
装好之后,设置好名称和内容就可以直接去repeater里面测试了,每次的token会自动刷新。
File Inclusion(文件包含)
<?php// The page we wish to display$file = $_GET[ 'page' ];// Input validationif( !fnmatch( "file*", $file ) && $file != "include.php" ) { // This isn't the page we want! echo "ERROR: File not found!"; exit;}?
这次的代码量很小,大概解读一下:
if( !fnmatch( "file*", $file ) && $file != "include.php" )
如果没有这个文件或者这个文件不是include.php,那么就不会执行,但是我们可以使用file协议绕过。
File Upload(文件上传)
先看一下high.php:
<?phpif( isset( $_POST[ 'Upload' ] ) ) { // Where are we going to be writing to? $target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/"; $target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] ); // File information $uploaded_name = $_FILES[ 'uploaded' ][ 'name' ]; $uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1); $uploaded_size = $_FILES[ 'uploaded' ][ 'size' ]; $uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ]; // Is it an image? if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) && ( $uploaded_size < 100000 ) && getimagesize( $uploaded_tmp ) ) { // Can we move the file to the upload folder? if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) { // No $html .= '<pre>Your image was not uploaded.</pre>'; } else { // Yes! $html .= "<pre>{$target_path} succesfully uploaded!</pre>"; } } else { // Invalid file $html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>'; }}?>
可以看到getimagesize( )函数来验证有没有相关的文件头等等,所以直接改格式不行,需要一个图片马儿,也会判断最后的'.'后的内容必须是jpg,jpeg,png三者之一。
图片马的制作很简单,打开cmd:
copy shell.php/b+test.png/a hack.png
简单的用记事本打开图片,在里面加入一句话也可以,然后我们用00截断的方式来绕过上传。
Insecure CAPTCHA(不安全验证码)
Insecure CAPTCHA,意思是不安全的验证码,CAPTCHA是Completely Automated Public Turing Test to Tell Computers and Humans Apart(全自动区分计算机和人类的图灵测试)的简称。
recaptcha_check_answer($privkey,$remoteip, $challenge,$response)
看一下代码:
<?phpif( isset( $_POST[ 'Change' ] ) ) { // Hide the CAPTCHA form $hide_form = true; // Get input $pass_new = $_POST[ 'password_new' ]; $pass_conf = $_POST[ 'password_conf' ]; // Check CAPTCHA from 3rd party $resp = recaptcha_check_answer( $_DVWA[ 'recaptcha_private_key' ], $_POST['g-recaptcha-response'] ); if ( $resp || ( $_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3' && $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA' ) ){ // CAPTCHA was correct. Do both new passwords match? if ($pass_new == $pass_conf) { $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 $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "' LIMIT 1;"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); // Feedback for user $html .= "<pre>Password Changed.</pre>"; } else { // Ops. Password mismatch $html .= "<pre>Both passwords must match.</pre>"; $hide_form = false; } } else { // What happens when the CAPTCHA was entered incorrectly $html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>"; $hide_form = false; return; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);}// Generate Anti-CSRF tokengenerateSessionToken();?>
可以判断出当$resp == False以及g-recaptcha-response != hidd3n_valu3或者HTTP_USER_AGENT != reCAPTCHA的时候,验证码为错误,$resp的值我们控制不了,是由recaptcha_check_answer( )决定的,所以我从g-recaptcha-response和HTTP_USER_AGENT入手。
我们更改HTTP_USER_AGENT的值为reCAPTCHA
添加g-recaptcha-response的值为hidd3n_valu3
就ok了
SQL Injection(SQL注入)
<?phpif( isset( $_SESSION [ 'id' ] ) ) { // Get input $id = $_SESSION[ 'id' ]; // Check database $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</pre>' ); // Get results while( $row = mysqli_fetch_assoc( $result ) ) { // Get values $first = $row["first_name"]; $last = $row["last_name"]; // Feedback for end user $html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); }?>
和文件包含一样的简洁,前端比low级多了一个弹出框:
由于多了一个页面,所以我们不能直接sqlmap-u这样的语法了,而且还有cookie和session的限制(可以填进去,看看usage)。
所以我们要用到--second-order,抓个包,将内容都放到1.txt中然后执行。
sqlmap -r 1.txt -p id --second-order "http://192.168.242.1/dvw/vulnerabilities/sqli/" --level 2
SQL Injection (Blind)
high.php
<?phpif( isset( $_COOKIE[ 'id' ] ) ) { // Get input $id = $_COOKIE[ 'id' ]; // Check database $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors // Get results $num = @mysqli_num_rows( $result ); // The '@' character suppresses errors if( $num > 0 ) { // Feedback for end user $html .= '<pre>User ID exists in the database.</pre>'; } else { // Might sleep a random amount if( rand( 0, 5 ) == 3 ) { sleep( rand( 2, 4 ) ); } // User wasn't found, so the page wasn't! header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); // Feedback for end user $html .= '<pre>User ID is MISSING from the database.</pre>'; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);}?>
抓包将cookie中参数id改为1’ and length(database( ))=4 #,显示存在,说明数据库名的长度为4个字符;
抓包将cookie中参数id改为1’ and length(substr(( select table_name from information_schema.tables where table_schema=database( ) limit 0,1),1))=9 #,显示存在,说明数据中的第一个表名长度为9个字符;
抓包将cookie中参数id改为1’ and (select count(column_name) from information_schema.columns where table_name=0×7573657273)=8 #,(0×7573657273 为users的16进制),显示存在,说明uers表有8个字段。
Weak Session IDs
high.php
<?php$html = "";if ($_SERVER['REQUEST_METHOD'] == "POST") { if (!isset ($_SESSION['last_session_id_high'])) { $_SESSION['last_session_id_high'] = 0; } $_SESSION['last_session_id_high']++; $cookie_value = md5($_SESSION['last_session_id_high']); setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], false, false);}?>
看到$cookie_value就是md5加密了last_session_id_high,last_session_id_high这个值初始为0,逐个+1然后md5加密,所以这个cookie校验对我们无效,构造payload使用火狐提交。
XSS (DOM)
high.php
<?php// Is there any input?if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) { # White list the allowable languages switch ($_GET['default']) { case "French": case "English": case "German": case "Spanish": # ok break; default: header ("location: ?default=English"); exit; }}?>
提交后url为:
http://192.168.159.129/vulnerabilities/xss_d/?default=English
<option value=''>English</option>
我们在里面插入Javascipt语句:
<option value=''>English #<script>alert(/xss/)</script></option>
这样两个标签都闭合,我们来看看效果:
XSS (Reflected)
high.php
<?phpheader ("X-XSS-Protection: 0");// Is there any input?if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) { // Get input $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] ); // Feedback for end user $html .= "<pre>Hello ${name}</pre>";}?>
居然直接正则把<script>过滤了,双写大小写绕过都不可以,但是我们还可以插别的标签,比如img比如body。
<img src=1.jpg>
我们可以看见,这个标签执行了。
XSS (Stored)
high.php
<?phpif( isset( $_POST[ 'btnSign' ] ) ) { // Get input $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); // Sanitize message input $message = strip_tags( addslashes( $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 = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $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)) ? "" : "")); // Update database $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); //mysql_close();}?>
和上面一样,正则过滤<script>,还是和上面一样,用img标签。
<img src=1.jpg onerror=alert(/adian/)>
Burpsuite抓包改参数:
弹出
CSP Bypass
Content Security Policy(CSP),内容(网页)安全策略,为了缓解潜在的跨站脚本问题(XSS攻击),浏览器的扩展程序系统引入了内容安全策略(CSP)这个概念。具体内容可以参见《Content Security Policy 入门教程》,类似白名单的一种机制。
<?php$headerCSP = "Content-Security-Policy: script-src 'self';";header($headerCSP);?><?phpif (isset ($_POST['include'])) {$page[ 'body' ] .= " " . $_POST['include'] . "";}$page[ 'body' ] .= '<form name="csp" method="POST"> <p>The page makes a call to ' . DVWA_WEB_PAGE_TO_ROOT . '/vulnerabilities/csp/source/jsonp.php to load some code. Modify that page to run your own code.</p> <p>1+2+3+4+5=<span id="answer"></span></p> <input type="button" id="solve" value="Solve the sum" /></form><script src="source/high.js"></script>';
high.js
function clickButton() { var s = document.createElement("script"); s.src = "source/jsonp.php?callback=solveSum"; document.body.appendChild(s);}function solveSum(obj) { if ("answer" in obj) { document.getElementById("answer").innerHTML = obj['answer']; }}var solve_button = document.getElementById ("solve");if (solve_button) { solve_button.addEventListener("click", function() { clickButton(); });}
在网上找到了一段代码:
if (isset ($_POST['include'])) {$page[ 'body' ] .= " " . $_POST['include'] . "";}
来接收参数,然后再构造payload就可以了。
Javascript
high.php
<?php$page[ 'body' ] .= <<<EOF<script src="/vulnerabilities/javascript/source/high.js"></script>EOF;?>
生成token的步骤总结:
*请认真填写需求信息,我们会在24小时内与您取得联系。