Black House


  • 首页

  • 标签

  • 分类

  • 归档

Python-dynamic typing

发表于 2019-03-07 | 分类于 Learning

Pre:

Question1:

1
2
3
4
>>> a = 3
>>> print type(a)

<type 'int'>

我们并没有显示声明a是个变量而且它的类型为int

那么python是怎么知道a是个变量而且知道它的类型的呢?


Question2:

执行以下代码

1
2
3
4
5
6
>>> a = 3
>>> a
3
>>> a = 'string'
>>> a
'string'

a 的类型怎么发生了变化?


以上2个问题涉及到python的一个重要的概念:动态类型(dynamic typing)

要搞清楚动态类型之前要先搞清楚3个重要的概念及其关系:

  • Object 对象
  • Reference 引用
  • Variable 变量

三个重要的概念:

Object:

对象是我们分配的内存块,有足够的空间去存储数据.

所以dict、list、string这些对象在内存中都是一段01序列

除了告诉解释器它是多少位后,它还要声明类型.

所以每个对象都有一个标准头部信息:类型标识符type designator来表示对象的类型.


Reference:

由于我们不能直接接触到存在内存中的对象,能操作的是变量名,所以我们要将变量和对象建立一个关系:引用reference

python的引用跟C语言的指针很像,因为引用就是用指针实现的.


Variable:

建立引用关系之后,我们就可以用变量名来操控对象了.

所以,变量是对对象的引用的一个符号名。


三者的关系:

变量和对象存储在内存的不同部分中,并链接了起来(由指针).

实际就是,执行一个赋值语句的时候,python会做三件事:

  1. 创建一个对象代表数值3
  2. 创建一个变量名a
  3. 将对象3和变量a关联起来


共享引用

1
2
>>> a = 3
>>> b = a

多个变量名同时指向同一个对象,称为共享引用Shared References.

1
2
3
>>> a = 3
>>> b = a
>>> a = 'spam'

变量a 改变了指向,指向到了'spam'对象

而变量b还是指向原来的3对象,所以值没有改变


refs:

  • 《Studing Learning Python》
  • Variables in Python
  • Python进阶09 动态类型
  • Dynamic Typing in Python
  • Python objects, types, classes, and instances - a glossary

Sqlmap源码-AttribDict属性字典

发表于 2019-03-05 | 分类于 SourceCodeReading

pre:

sqlmap在处理数据的时候大量使用了自定义的AttribDict属性字典这个数据类型.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# sqlmap paths
paths = AttribDict()

# object to store original command line options
cmdLineOptions = AttribDict()

# object to store merged options (command line, configuration file and default options)
mergedOptions = AttribDict()

# object to share within function and classes command
# line options and settings
conf = AttribDict()

# object to share within function and classes results
kb = AttribDict()

所以有必要看看AttribDict这个类.

顺便总结一下自己不熟悉的相关知识点.


AttribDict组成:

这个类通过override了几个super method.

修改原生的dict定制成了自己项目需要的属性字典.


定义:

1
2
3
4
5
6
7
8
9
"""
This class defines the sqlmap object, inheriting from Python data
type dictionary.

>>> foo = AttribDict()
>>> foo.bar = 1
>>> foo.bar
1
"""

原来的字典的用法:dict1["key"]
现在的自定义字典的用法:dict1.key


__init__ 初始化

1
2
3
4
5
6
7
8
9
10
11
12
def __init__(self, indict=None, attribute=None):
if indict is None:
indict = {}

# Set any attributes here - before initialisation
# these remain as normal attributes 在初始化之前设置通用属性
self.attribute = attribute
dict.__init__(self, indict)
self.__initialised = True # 初始化标志

# After initialisation, setting attributes
# is the same as setting an item 初始化之后

__setattr__ 对一个属性赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def __setattr__(self, item, value):
"""
Maps attributes to values
Only if we are initialised 只对初始化过的实例赋值
"""

# This test allows attributes to be set in the __init__ method
# 允许在初始化的时候设置属性
if "_AttribDict__initialised" not in self.__dict__:
return dict.__setattr__(self, item, value)


# Any normal attributes are handled normally
elif item in self.__dict__: # 如果已经设置过了
dict.__setattr__(self, item, value)

else: # 没设置过的
self.__setitem__(item, value)

例如执行以下代码

1
2
kb = AttribDict()
kb.a = 1

kb = AttribDict()执行完,也就初始化完kb这个实例了,对象的属性储存在对象的__dict__属性中,这个时候它的__dict__为

1
{'_AttribDict__initialised': True, 'attribute': None}

执行kb.a=1即对实例的属性赋值的时候,就会隐式调用到__setattr__这个super method.

首先会在__dict__搜索是否初始化过的标志属性

  • 如果未初始化过的话,返回一个dict.

  • 如果初始化过的实例,会根据key在__dict__搜索

    • 如果这个属性已经设置过的话,会调用

      1
      2
      dict.__setattr__(self, item, value)
      # 相当于执行`self.itme = value`,对一个属性赋值(覆盖已有属性)
    • 如果这个属性没设置过的话,会调用

      1
      2
      self.__setitem__(item, value)
      # 相当于执行`self[key] = val`,对新索引值赋值

__getattr__ 访问一个不存在的属性

1
2
3
4
5
6
7
8
9
10
def __getattr__(self, item):
"""
Maps values to attributes
Only called if there *is NOT* an attribute with this name
"""

try:
return self.__getitem__(item) # 通过key获取value
except KeyError:
raise AttributeError("unable to access item '%s'" % item)
1
2
kb = AttribDict()
print kb.a

执行print kb.a的时候,访问一个不存在的属性的时候,会隐式调用__getattr__这个super method.

当属性不存在的时候,__getattr__会raise an AttributeError exception.

  • 默认的__getattr__的报错:

    1
    AttributeError: 'AttribDict' object has no attribute 'a'
  • 修改过的__getattr__的报错:

    1
    AttributeError: unable to access item 'a'

额,效果只是让错误输出更友好而已….


__getstate__ 序列化

1
2
def __getstate__(self):
return self.__dict__

代替对象的dict属性被保存。

当对象pickled,你可返回一个自定义的状态被保存。
当对象unpickled时,这个状态将会被__setstate__使用。


__setstate__ 序列化

1
2
def __setstate__(self, dict):
self.__dict__ = dict

对象unpickled时,如果__setstate__定义对象状态会传递来代替用对象的dict属性。

这正好跟__getstate__手牵手:当二者都被定义了,你可以描述对象的pickled状态,任何你想要的。


__deepcopy__ 深复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def __deepcopy__(self, memo):
retVal = self.__class__()
memo[id(self)] = retVal

for attr in dir(self):
if not attr.startswith('_'):
value = getattr(self, attr)
if not isinstance(value, (types.BuiltinFunctionType, types.FunctionType, types.MethodType)):
setattr(retVal, attr, copy.deepcopy(value, memo))

for key, value in self.items():
retVal.__setitem__(key, copy.deepcopy(value, memo))

return retVal

对于简单的 object,用 shallow copy 和 deep copy 没区别

复杂的 object, 如 list 中套着 list 的情况,shallow copy 中的 子list,并未从原 object 真的「独立」出来。也就是说,如果你改变原 object 的子 list 中的一个元素,你的 copy 就会跟着一起变。这跟我们直觉上对「复制」的理解不同。

嵌套的复杂object,为了保证数据的独立性,要尽量的使用deepcopy.


好处:

kb作为一个全局的字典,保留了相关的配置信息,很多代码里都会用到这个全局的字典,暂时能想到这样的好处就是

  • 大篇幅代码都用到的话,dict1.key比dict1["key"]看起来更简洁.更pythonic.
  • 可以自定义更加友好的错误输出提示信息.
  • 扫描中断需要继续的话,可以自定义序列化的内容,便于自己保存对象.

相关知识点:

special method:

特殊之处在哪呢?

它的特殊之处在于:

如果把Python当成一个framework的话,这些预留的特殊方法相当于接口interface.

你可以通过这些接口,使得your-object就跟built-in object高度一致,也能复用到built in-object原有的强大的功能.


有哪些类型的接口呢?

可以在以下这些方面定制你自己的类:

  1. 构造和初始化
  2. 控制属性访问
  3. 创建自定义容器
  4. 反射
  5. 可调用的对象
  6. 上下文管理
  7. 创建对象描述器
  8. 复制

什么时候会用到特殊方法呢?

原有的数据类型的功能不能满足你的要求.

例子:

  1. 你可以像sqlmap这里的AttribDict在原生字典的基础上构造自己符合自己项目的数据类型.
  2. 自带的dict是无序的,如果你想用有序的字典.你可以用collections模块的OrderedDict.而这个OrderedDict就是在dict的基础上拓展来的.

__dict__ 对象的属性系统:

  • 对象的属性储存在对象的__dict__属性中
  • __dict__为一个词典,键为属性名,对应的值为属性本身.

来源:

  • 类属性(class attribute): 类定义 or 根据类定义继承来的
  • 对象属性(object attribute): 对象实例定义的

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
class bird(object):
feather = True

class chicken(bird):
fly = False
def __init__(self, age):
self.age = age

summer = chicken(2)

print(bird.__dict__)
print(chicken.__dict__)
print(summer.__dict__)
1
2
3
4
5
6
7
8
# bird对象属性 比如feather
{'__dict__': <attribute '__dict__' of 'bird' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'bird' objects>, 'feather': True, '__doc__': None}

# chicken对象属性 比如fly和__init__方法
{'fly': False, '__module__': '__main__', '__doc__': None, '__init__': <function __init__ at 0x2b91db476d70>}

# summer对象属性 比如age
{'age': 2}

有一些属性,比如__doc__,并不是由我们定义的,而是由Python自动生成。

此外,bird类也有父类,是object类(正如我们的bird定义,class bird(object))。

这个object类是Python中所有类的父类。

可以看到,Python中的属性是分层定义的,比如这里分为object/bird/chicken/summer这四层。

当我们需要调用某个属性的时候,Python会一层层向上遍历,直到找到那个属性。(某个属性可能出现再不同的层被重复定义,Python向上的过程中,会选取先遇到的那一个,也就是比较低层的属性定义)。

当我们有一个summer对象的时候,分别查询summer对象、chicken类、bird类以及object类的属性,就可以知道summer对象所有的__dict__,就可以找到通过对象summer可以调用和修改的所有属性了.

refs: Python深入03 对象的属性


deep copy和shadow copy:

  • 我们寻常意义的复制就是深复制,即将被复制对象完全再复制一遍作为独立的新个体单独存在。所以改变原有被复制对象不会对已经复制出来的新对象产生影响。
  • 而浅复制并不会产生一个独立的对象单独存在,他只是将原有的数据块打上一个新标签,所以当其中一个标签被改变的时候,数据块就会发生变化,另一个标签也会随之改变。这就和我们寻常意义上的复制有所不同了。
  • 对于简单的 object,用 shallow copy 和 deep copy 没区别
  • 复杂的 object, 如 list 中套着 list 的情况,shallow copy 中的 子list,并未从原 object 真的「独立」出来。也就是说,如果你改变原 object 的子 list 中的一个元素,你的 copy 就会跟着一起变。这跟我们直觉上对「复制」的理解不同。

refs: Python-copy()与deepcopy()区别


refs:

  • Subclassing dict: should dict.init() be called?

  • Python 方法调用机制

  • Python, override__getstate__() and setstate()

  • sqlmap源码阅读之基础(dict,sys)

  • Python 魔术方法 - Magic Method

  • Python 魔术方法指南

  • 3. Data model

  • Python深入03 对象的属性

  • Python新式类与经典类的区别

  • When to deepcopy classes in Python

  • Python-copy()与deepcopy()区别

Sqlmap源码-checkSameHost函数的疑问

发表于 2019-03-01 | 分类于 SourceCodeReading

Pre:

在看sqlmap源码的时候,看了它的crawler,用到了checkSameHost函数.

跟进到lib.core.common公共函数里看checkSameHost函数,产生了一些疑问


checkSameHost函数:

函数作用

用来判断一堆urls是否是相同主机的.

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
def checkSameHost(*urls):
"""
Returns True if all provided urls share that same host
>>> checkSameHost('http://www.target.com/page1.php?id=1', 'http://www.target.com/images/page2.php')
True
>>> checkSameHost('http://www.target.com/page1.php?id=1', 'http://www.target2.com/images/page2.php')
False
"""
# 用了*urls可以接受任意数量的参数,并放入tuple里,由于不知道传入url的个数是多少,所以要适当的进行判断
if not urls:
return None
elif len(urls) == 1: # 1个url不用判断
return True
else:
def _(value): # _ 这个用法应该是匿名函数的意思吧?
if value and not re.search(r"\A\w+://", value):
# \A 文本开头
# \w匹配英文字母、数字或下划线,等价于[a-zA-Z0-9_]。
# + 量词 — 匹配 1 至 无穷 次
# :// 字面匹配字符
value = "http://%s" % value # 没有协议的话自动加上http协议.这个判断可用来处理输入时有没带协议的情况,方便urlparse.urlparse函数进行解析
return value
return all(re.sub(r"(?i)\Awww\.", "", urlparse.urlparse(_(url) or "").netloc.split(':')[0]) == re.sub(
r"(?i)\Awww\.", "", urlparse.urlparse(_(urls[0]) or "").netloc.split(':')[0]) for url in urls[1:])
# all(iterable) Return True if all elements of the iterable are true (or if the iterable is empty)
# urlparse.urlparse(_(url) or "").netloc.split(':')[0] 域名服务器
# 正则表达式 r"(?i)\Awww\." (?!) Assert that the Regex below does not match
# www.开头的域名,www.baidu.com会被处理成baidu.com
# 遍历看,通过域名判断是否是sameHost

测试:

  • http://www.target.com/page1.php?id=1, http://www.target.com/images/page2.php

    • True
  • http://www.target.com/page1.php?id=1, http://www.target2.com/images/page2.php

    • False
  • http://xxx.com,http://www.xxx.com

    • True
  • http://aa.xxx.com,http://bb.xxx.com

    • False
    • ?????

疑问:

很多时候,http://aa.xxx.com,http://bb.xxx.com这两个子域名是可以解析同一个ip上的,这种情况下sameHost判断应该为True才对,为什么这里为False呢?

answer:

这里的sameHost应该是有歧义
严格意义上,是否是sameHost应该通过ip来判断,但是如果url很多的话,都去其解析域名对应的ip,会消耗时间

广义上看,这里的sameHost应该指的是same website.

通过域名来判断是否是相同的网站,是ok的.


域名与ip的关系:

常见的情况下,一个域名与一个ip是对应的.

但是域名与ip的关系不一定是一对一的关系,可以是多对一或者是一对多的关系.

  • 多个域名解析到一个ip:

    • IIS中怎么配置虚拟主机
    • IS可否将多个域名配置到同一个IP/端口
  • 一个域名解析到多个ip:

    • 负载均衡实现,一个域名对应多个IP地址

记录-Centos7 Docker部署漏洞环境

发表于 2019-02-27 | 分类于 Dev

docker安装:

  • 查询可用版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@localhost ~]# yum list docker-ce --showduplicates | sort -r
已加载插件:fastestmirror, langpacks
可安装的软件包
Loading mirror speeds from cached hostfile
docker-ce.x86_64 3:18.09.2-3.el7 docker-ce-stable
docker-ce.x86_64 3:18.09.1-3.el7 docker-ce-stable
docker-ce.x86_64 3:18.09.0-3.el7 docker-ce-stable
docker-ce.x86_64 18.06.3.ce-3.el7 docker-ce-stable
docker-ce.x86_64 18.06.2.ce-3.el7 docker-ce-stable
docker-ce.x86_64 18.06.1.ce-3.el7 docker-ce-stable
docker-ce.x86_64 18.06.0.ce-3.el7 docker-ce-stable
docker-ce.x86_64 18.03.1.ce-1.el7.centos docker-ce-stable
docker-ce.x86_64 18.03.0.ce-1.el7.centos docker-ce-stable
docker-ce.x86_64 17.12.1.ce-1.el7.centos docker-ce-stable
docker-ce.x86_64 17.12.0.ce-1.el7.centos docker-ce-stable
docker-ce.x86_64 17.09.1.ce-1.el7.centos docker-ce-stable
docker-ce.x86_64 17.09.0.ce-1.el7.centos docker-ce-stable
docker-ce.x86_64 17.06.2.ce-1.el7.centos docker-ce-stable
docker-ce.x86_64 17.06.1.ce-1.el7.centos docker-ce-stable
docker-ce.x86_64 17.06.0.ce-1.el7.centos docker-ce-stable
docker-ce.x86_64 17.03.3.ce-1.el7 docker-ce-stable
docker-ce.x86_64 17.03.2.ce-1.el7.centos docker-ce-stable
docker-ce.x86_64 17.03.1.ce-1.el7.centos docker-ce-stable
docker-ce.x86_64 17.03.0.ce-1.el7.centos docker-ce-stable

如果使用yum install docker-ce下载下来的docker版本是很有可能是第一个3:18.09.2-3.el7,运行的时候会报错,要指定centos的版本下载才能正常运行.

  • 安装指定版本
1
[root@localhost ~]# yum install docker-ce-18.03.1.ce-1.el7.centos
  • 启动docker
1
[root@localhost ~]# systemctl start docker
  • 测试安装结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@localhost ~]# docker run hello-world                       

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/

For more examples and ideas, visit:
https://docs.docker.com/get-started/

无报错,则正常


漏洞环境下载:

  • Pre-Built Vulnerable Environments Based on Docker-Compose

根据readme 安装

  • 快速搭建各种漏洞环境(Various vulnerability environment

  • CentOS7下安装Docker-Compose

  • cannot uninstall a distutils installed project’


开机自启:

docker 服务开机自启

1
2
[root@localhost ~]# systemctl enable docker.service
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.

漏洞环境容器的开机自启

1
2
# 拉取镜像到本地
[root@localhost ~]# docker pull medicean/vulapps:s_struts2_s2-032
1
2
3
# 启动环境
[root@localhost ~]# docker run -d -p 80:8080 medicean/vulapps:s_struts2_s2-032
6aa16caac712adcc0f5410dbed2aaa14a303ede2378311f0c2222b1901bcef25
1
2
3
4
5
# 查看CONTAINER ID 
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6aa16caac712 medicean/vulapps:s_struts2_s2-032 "/usr/local/tomcat/b…" 22 seconds ago Up 21 seconds 0.0.0.0:80->8080/tcp keen_carson
ee8ea1d1acdd medicean/vulapps:r_redis_1 "/start.sh" 30 minutes ago Up 4 minutes 22/tcp, 0.0.0.0:6379->6379/tcp redisvul
1
2
3
# 参数always:始终重启,更新设为开机自启
[root@localhost ~]# docker update --restart=always 6aa16caac712
6aa16caac712

重启机子,后允许docker ps查看是否自启成功

其他常用命令:

删除容器:

1
2
3
4
5
# 查看所有container
root@Linx:~/apache# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e6318265a202 httpd:centos "/run.sh" 5 minutes ago Exited (127) 5 minutes ago httpd1
eaf9ee72f448 httpd:centos "/run.sh" 6 minutes ago httpd
1
2
3
# "docker rm 容器id"来删除一个终止状态的容器;若要删除一个运行中的容器,需要加-f参数。
root@Linx:~/apache# docker rm e6318265a202
e6318265a202

删除镜像:

1
2
3
4
5
6
7
# 查看已有的docker镜像
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 47b19964fb50 3 weeks ago 88.1MB
hello-world latest fce289e99eb9 8 weeks ago 1.84kB
centos latest 1e1148e4cc2c 2 months ago 202MB
medicean/vulapps s_struts2_s2-048 14cac47d977d 19 months ago 348MB
1
2
# 删除images,通过image的id来指定删除谁
docker rmi <image id>

others:

这样就可以使用一台机子来专门安装漏洞环境,方便poc测试、调试。


refs:

  • Pre-Built Vulnerable Environments Based on Docker-Compose
  • 快速搭建各种漏洞环境(Various vulnerability environment

Breacher-Go高并发管理员后台爆破工具

发表于 2019-02-23 | 分类于 SecurityTools

preface :

Breacher是由python写的多线程管理员后台爆破工具

出于练手的目的,打算用golang重新造个轮子.

github:Breacher-Go

并发:

python里面自然用得多的是多线程.
而go里面自然要用到goroutine

go goroutine

语法:

在Go语言中,每一个并发的执行单元叫作一个goroutine

当一个程序启动时,其主函数即在一个单独的goroutine中运行,我们叫它main goroutine。

新的goroutine会用go语句来创建。

在语法上,go语句是一个普通的函数或方法调用前加上关键字go。

go语句会使其语句中的函数在一个新创建的goroutine中运行。而go语句本身会迅速地完成。

1
2
f()    // call f(); wait for it to return
go f() // create a new goroutine that calls f(); don't wait

在Go语言中,语言本身就已经实现和支持了并发,所以写起并发来相当方便.

goroutine的优势:

gouroutine其实就是一种协程,类似其他语言中的coroutine, 是在编译器或虚拟机层面上的多任务。

它可以运行在一个或多个线程上,但不同于线程,它是非抢占式的,所以协程很轻量。

简单的并发方案1

简单的做法:

  • 将需要处理的Url分成n等份
  • 开启n个goroutine去消费掉url
  1. 将需要处理的Url分成n等份
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func divided(links []string, goroutineNum int) [][]string {
chunkSize := (len(links) + goroutineNum - 1) / goroutineNum

var dividedPath [][]string
for i := 0; i < len(links); i += chunkSize {
end := i + chunkSize

if end > len(links) {
end = len(links)
}

dividedPath = append(dividedPath, links[i:end])
}

return dividedPath

}

2.开启n个goroutine去消费掉url

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dividedLinks := divided(collected_path, 5)

var wg sync.WaitGroup
for _, link := range dividedLinks {
fmt.Printf("%#v\n", len(link))
wg.Add(1) // Increment the WaitGroup counter.
go func(link []string) {
// Launch a goroutine to fetch the link.
scan(start_url, link)
// Fetch the link.
wg.Done()
}(link)
}
wg.Wait() // Wait for all goroutines to finish.

速度比较:

自身速度比较:

go 不用并发消耗的时间:

不同网络环境下的消耗时间:

  1. elapsed time: 1.951978378s (5个goroutine)

  2. elapsed time: 11.353934431s

  3. elapsed time: 35.50535812s (5个goroutine)

  4. elapsed time: 2m32.608078161s

与python的速度比较:

python:
2个线程:74.5581650734s

go:
2个goroutine
elapsed time: 1m10.233281137s

速度几乎是一样的,并非是想象中那样,go会比python快很多.

question: 什么场景下goroutine会比python的thread有优势?


减少误报:

最快的方式:

最快的方式就是用head请求,然后通过response code判断,如果是200则认为存在.

但是这样在实际应用过程中,很有可能会产生大量的误报.

使用御剑爆破网站路径的时候,同理也会产生大量的误报.

例子:

Response code是200

大量误报:

误报结果:

原因:

暂时能想到的原因有:

  • 网站本身会自定义错误或者404页面
  • 网站有防护设备,会自动重定向到别的页面

解决方案:

找到管理员的后台的目的,通常是为了暴力破解,尝试用弱密码进入到管理员的后台.

那么就是有个共同点就是有登录页面

密码框常用到是input标签的 type属性设为password

解决方案:

  • 用get方法获取登录页面源码
  • Response code 为200的同时,页面源码含有type="password"

结果确实是大大减少了误报

  1. 大字典的情况下,get方法的速度肯定会比head方法慢很多,哎,速度与准确性常常难以并存啊~
  2. 减少误报的同时,则会产生漏报问题…

refs:

  • 图解 Go 并发编程
  • 第八章 Goroutines和Channels
  • Golang并发编程
  • Go计算运行的时间
  • Slice chunking in Go

question:

  1. 什么场景下goroutine会比python的thread有优势?
  2. 减少误报的同时如何减少漏报?

Metinfo 6.x存储型XSS(CVE-2018-20486) 漏洞分析

发表于 2019-02-22 | 分类于 VulnAnalysis

安装漏洞环境:

漏洞复现:

poc:[Host]/admin/login/login_check.php?url_array[]=<script>alert(1)</script>&url_array[]=a

管理员登录后台-安全与效率,触发xss

漏洞分析:

粗略猜测:

引入了外部变量,直接修改了数据库该值

/MetInfo6.1.3/app/system/safe/admin/index.class.php

查询数据库发现被是加密后的字段:

解密后应该就是<script>alert(1)</script>


跟踪分析:

common.inc.php

1
2
3
4
5
6
foreach(array('_COOKIE', '_POST', '_GET') as $_request) {
foreach($$_request as $_key => $_value) {
$_key{0} != '_' && $$_key = daddslashes($_value,0,0,1);
$_M['form'][$_key]=daddslashes($_value,0,0,1);
}
}

这个时候,引入了外部变量_COOKIE,_POST, _GET

这个循环会遍历全局变量_COOKIE,_POST, _GET,然后对里面的值进行过滤,然后赋值到$_M[form]

进入循环之前,$_M变量的值为

结束循环时,出现了变量覆盖,$_M变量的值为

code mix with data.

login_check.php

payload经过authcode函数加密后.更新入了表met_config里的met_adminfile字段


攻击者将payload注入到数据库后,管理员访问后台的安全与效率,触发xss时发生了什么?

###

load_class.php 加载模块

加载了app/system/index/admin/下的index 类,调用里面的doindex函数

index 类继承了 admin类

1
class index extends admin

admin 类继承了 common类

common.class.php

common类 构造函数里面,加载了全站的配置数据

load_config_global函数,先读取了数据库的配置到$M全局数组中,

此时的met_adminfile还没解密

经过authcode解密函数后,

可以看到met_adminfile就是我们的payload

admin.class.php

执行check()函数:

将$M全局数组里的值赋给了$met_adminfile

加载页面:

先加载渲染了侧边栏、头部底部之后,最后才加载渲染/app/system/safe/admin/index.class.php


流程:

  • login_check.php 10行 包含了../include/common.inc.php

  • common.inc.php 79-84行,循环赋值给$M数组时产生了变量覆盖

  • login_check.php 13-18行,将payload加密后更新入了表met_config里的met_adminfile字段


others:

造成变量覆盖时使用的语法.

1
2
3
4
$hell="abc"; $$hell="def";等同于$abc="def"

$_request = _GET
$$_request = $_GET

这是个trick?


思考:

  • 善用搜索,加对断点可以加快跟踪调试的速度
  • 一开始粗略猜测,可以知道大体的思路
  • 对前端页面进行元素查看,也可以方便加断点,加快速度

refs:

  • php下session_cache_limiter(private,must-revalidate)–表单填写内容不丢失
  • Metinfo 6.x存储型XSS分析(CVE-2018-20486)

记录-简单的代码离线更新方案

发表于 2019-02-13 | 分类于 Dev

Pre:

部署在内网的主机,无法联网,需要在离线的状态下进行更新.


方案:

使用git 的git format-patch或者 git bundle

两者的区别:

Which is better to use and why? git format-patch or git bundle?

git bundle only makes sense if you transfer a changeset from one git-repository to another, whereas git format-patch creates standard diff-files which can be inspected easily and applied to a non-gitified source tree as well. on the other hand git bundle creates a single file, which is easier to transport than the multi-file output of git format-patch

git bundle只产生一个文件,只能应用于同一个仓库,而git format-patch会产生多个文件,不仅限于同一个仓库.


git bundle:

使用场景:

  • 有可能你的网络中断了,但你又希望将你的提交传给你的合作者们。
  • 可能你不在办公网中并且出于安全考虑没有给你接入内网的权限。
  • 可能你的无线、有线网卡坏掉了。
  • 可能你现在没有共享服务器的权限,你又希望通过邮件将更新发送给别人,却不希望通过 format-patch 的方式传输 40 个提交。

bundle 命令会将 git push 命令所传输的所有内容打包成一个二进制文件,你可以将这个文件通过邮件或者闪存传给其他人,然后解包到其他的仓库中。


git bundle 使用命令:

创建:

生成repo.bundle的文件,该文件包含了所有重建该仓库master分支所需的数据

1
git bundle create repo.bundle HEAD master

在使用 bundle 命令时,你需要列出所有你希望打包的引用或者提交的区间。 如果你希望这个仓库可以在别处被克隆,你应该像例子中那样增加一个 HEAD 引用。

应用:

克隆项目

1
git clone repo.bundle repo

git format-patch:

UNIX世界的软件开发大多都是协作式的,因此,Patch(补丁)是一个相当重要的东西,因为几乎所有的大型UNIX项目的普通贡献者,都是通过 Patch来提交代码的。作为最重要的开源项目之一,Linux,也是这样的。普通开发者从软件仓库clone下代码,然后写入代码,做一个Patch, 最后用E-mail发给Linux Kernel的维护者就好了。Git最初作为Linux的版本控制工具,提供了透明、完整、稳定的Patch功能。

我们先介绍一下Patch是什么。如果一个软件有了新版本,我们可以完整地下载新版本的代码进行编译安装。然而,像Linux Kernel这样的大型项目,代码即使压缩,也超过70MB,每次全新下载是有相当大的代价的。然而,每次更新变动的代码可能不超过1MB,因此,我们只 要能够有两个版本代码的diff的数据,应该就可以以极低的代价更新程序了。因此,Larry Wall开发了一个工具:patch。它可以根据一个diff文件进行版本更新。

不过在git中,我们没有必要直接使用diff和patch来做补丁,这样做既危险又麻烦。git提供了两种简单的patch方案。一是用git diff生成的标准patch,二是git format-patch生成的Git专用Patch。

git format-patch 使用命令:

创建patch:

某次提交(含)之前的几次提交:n指从sha1 id对应的commit开始算起n个提交

1
git format-patch 【commit sha1 id】-n

eg:

1
git format-patch  2a2fb4539925bfa4a141fe492d9828d030f7c8a8 -2

某两次提交之间的所有patch:

1
git format-patch 【commit sha1 id】..【commit sha1 id】

eg:

1
git format-patch  2a2fb4539925bfa4a141fe492d9828d030f7c8a8..89aebfcc73bdac8054be1a242598610d8ed5f3c8

应用patch:

检查patch/diff是否能正常打入:

1
2
git apply --check 【path/to/xxx.patch】
git apply --check 【path/to/xxx.diff】

打入patch/diff:

1
2
git apply 【path/to/xxx.patch】
git apply 【path/to/xxx.diff】

Summary:

git bundle可以理解为全量更新
git format-patch可以理解为增量更新

为了方便,还是选择了git bundle,因为使用git format-patch的过程,容易出现patch打不上导致更新中断的问题.

不过对于更严谨的更新方案的话,还是应该选择git format-patch的.


大体思路:

  • 更新包生成端:
    • 分别生成各个项目的bundle文件
    • 加密压缩成zip文件
  • 更新包使用端:
    • 用密钥解密压缩包
    • 到指定目录移除原来的项目,执行git clone


refs:

  • 巡风在隔离网络环境下的离线更新方案
  • 携程无线离线包增量更新方案实践
  • 根据git对比分支与tag之间的文件变动,生成补丁包
  • git打两个版本的增量包
  • Git 打补丁– patch 和 diff 的使用(详细)
  • Git 工具 - 打包
  • Bundle a local Git project with all uncommitted changes and stashes (git bundle on steroids).
  • git 打补丁方法总结
  • Git打补丁常见问题
  • Git的Patch功能

获取masscan的扫描进度

发表于 2019-01-16 | 分类于 SecurityTools

Preface:

一开始打算通过subprocess模块来调用masscan,然后获取它输出的进度。

但是发现它的进度输出是覆盖刷新输出的。

这个时候,subprocess就无法获得它的输出了。

想了下,masscan这个刷新输出应该是优化过的结果,避免打印出太多重复无效的信息。

这个时候,我们只要修改一下它的输出就可以达到效果.


修改源码:

搜索了一下,参考:C语言实现在控制台同一行覆盖刷新输出,以及’\b’退格控制字符的使用

知道printf("\r");是可以做到刷新输出的效果的。

搜索关键字:rate:

定位到main-status.c这个文件

修改这一行.将覆盖刷新输出改成 换行输出。

重新编译运行.

可以看到效果:


获取进度百分比:

1
2
3
4
5
6
7
8
9
10
def getPercentageNum(content):
'''
正则匹配百分比数字
'''
re_pattern = r"(\d{1,2}.\d{1,2})\%"
num_match_list = re.findall(re_pattern, content)
if num_match_list:
return num_match_list[-1]
else:
return None
1
2
3
4
5
6
7
8
9
10
cmd = shlex.split(shell_cmd)
print cmd

p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while p.poll() is None:
line = p.stdout.readline()
line = line.strip()
if line:
print('percentage: [{}]'.format(line))
print getPercentageNum(line)

通过上面两个函数,就可以获取到 扫描的进度百分比了.

另一种思路:

开启一个msscan,按下回车键的时候,会再次输出进度。

或许可以与进程通信,然后发送信号,来获取进度信息。

ThinkPHP 5.0.0~5.0.23 RCE 漏洞分析

发表于 2019-01-14 | 分类于 VulnAnalysis

漏洞复现

未开启debug模式,

1
2
3
4
5
6
7
url: 

http://tp1-11:8888/public/index.php?s=captcha

post:

_method=__construct&filter[]=system&method=get&get[]=whoami

漏洞分析:

定位关键代码:

第525行: 读取了配置文件的var_method,

并赋值

1
$this->method = _method

第526行:

引用了外部变量$POST

1
$this->{$this->method}($_POST);

执行结果:

将Request类的method覆盖了,这个时候将调用Request类构造函数__construct

构造函数引入了外部变量,准备创建一个新实例

这个实例的属性

  • $method被设置成了get
  • $get方法的请求参数被设置成了一个array,值为whoami
  • 全局过滤规则$filter被设置成了一个array,值为system

这个时候实例的初始化完成了。

跳回到了Route.php检测URL路由的函数check(),这个时候的method值为get

经过一连串检测之后,

App.phprun()函数对请求执行调用分发

继续跟进,

调用栈:

  • param()获取当前请求的参数
  • method()当前的请求类型
  • server()获取server参数
  • input()获取变量 支持过滤和默认值
  • getFilter()获取过滤方法

这个时候我们之前创建的请求实例的filter方法为system

继续

  • param()获取当前请求的参数

  • get()设置获取GET参数

  • input()获取变量 支持过滤和默认值

  • array_walk_recursive()
  • filterValue递归过滤给定的值

执行结果:

refs:

  • ThinkPHP 5.0.0~5.0.23 RCE 漏洞分析
  • php://input

子域名搜集

发表于 2019-01-11 | 分类于 SecurityTools

为什么要搜集子域名?

这些子域名都是与企业的资产相关联的.发现更多的子域名,意味着发现了更多的企业资产.

攻击者的角度:

对于攻击者来说,意义在于可以发现更多的攻击点,增大攻击面attack face,接着选取一个最脆弱的目标进行攻击.

在《Metasploit渗透测试指南》里面有提到,真正的攻击者是不会造成很多噪声的,意味着他会选择一条代价最小、收益最高的攻击路径,来获取他目标系统的高权限.

为此他需要先找到一个最脆弱的目标.也就是找到企业资产中最脆弱的一部分来进行攻击.

而通过对子域名进行搜集,便是发现脆弱资产当中必要的一步.

防御者的角度:

对于防御者来说,意义在于能够对资产进行更好的管理.

企业大部分做的安全工作都是围绕企业的资产来进行的。

可现状是,由于企业大、业务线多、管理难等问题,很多时候企业会匆忙上线一个新的应用或服务,这样就会造成出现很多“在野资产”.

这种“在野资产”往往是等到出现安全事件后,去追责才会被发现。

这种时候,如果能够提前对新增、新上线的资产进行发现的话,就能更好的保障企业的资产安全.

而通过对子域名进行搜集,便是发现“在野资产”当中必要的一步.


搜集途径:

外部搜集途径:

在外部,可以通过很多方式进行搜集,这张思维导图列出来的已经比较详细了。

其他接口:

  • 百度云观测接口

内部搜集途径:

除了通过互联网,在外部进行必要的搜集以外,也是需要内部对子域名进行搜集来进行补充的.


refs:

  • OWASP-Nettacker
  • esoteric-sub-domain-enumeration-techniques
  • teemo
  • 《Metasploit渗透测试指南》
1…789
Foolisheddy

Foolisheddy

nothing to say :)

87 日志
14 分类
14 标签
© 2022 Foolisheddy
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4