系统开发遇到的一些问题——系列二

最近一直忙着完成一个项目系统的收尾工作,由于是第一次完整的参与一个系统开发,所以这期间遇到了大大小小的问题。直到这几天手头的事少了一些,才能有时间坐下来将之前遇到的问题做一个总结.

2. 利用PHP进行IP定位的问题

系统其中一处需要用到定位用户地址的功能,考虑通过PHP来获取,其中涉及到四个参数REMOTE_ADDRHTTP_CLIENT_IPHTTP_X_FORWARDED_FORHTTP_VIA

在一开始,我们先了解一下关于代理服务器的一些问题。

根据匿名程度区分:

\ 匿名程度 解释
1 高度匿名代理 高度匿名代理会将数据包原封不动的转发,在服务端看来就好像真的是一个普通客户端在访问,而记录的IP是代理服务器的IP。
2 普通匿名代理 普通匿名代理会在数据包上做一些改动,服务端上有可能发现这是个代理服务器,也有一定几率追查到客户端的真实IP。代理服务器通常会加入的HTTP头有HTTP_VIAHTTP_X_FORWARDED_FOR
3 透明代理 透明代理不但改动了数据包,还会告诉服务器客户端的真实IP。这种代理除了能用缓存技术提高浏览速度,能用内容过滤提高安全性之外,并无其他显著作用。(最常见的例子是:内网中的硬件防火墙)
4 间谍代理 间谍代理指组织或个人创建的,用于记录用户传输的数据,然后进行研究、监控等目的代理服务器。

这个小知识,在后面解释的时候会用到,下面我们接着回归正题。

REMOTE_ADDR 是远端IP,默认来自tcp连接,即你的客户端跟你的服务器“握手”时候的IP。如果使用了“匿名代理”,REMOTE_ADDR将显示代理服务器的IP。如:a->b(proxy)->c ,如果c通过REMOTE_ADDR ,只能获取到b的IP,获取不到a的IP了。另外:该IP想篡改很难实现,在传递直到生成相应的值,都是直接生成的。

HTTP_X_FORWARDED_FORHTTP_CLIENT_IP是为了能在大型网络中,获取到最原始用户IP,或者代理IP地址。对HTTp协议进行扩展。定义的实体头。
HTTP_CLIENT_IP 是代理服务器发送的HTTP头。如果是“超级匿名代理”,则返回none值。同样,REMOTE_ADDR也会被替换为这个代理服务器的IP。

当你使用了代理时,web服务器就不知道你的真实IP了,为了避免这个情况,代理服务器通常会增加一个叫做X_Forwarded_For的头信息,把连接它的客户端IP(即你的上网机器IP)加到这个头信息里,这样就能保证网站的web服务器能获取到真实IP。

1
HTTP_X_FORWARDED_FOR = clientip,proxy1,proxy2

所有IP用”,”分割,正常情况下XFF中最后一个IP地址是最后一个代理服务器的IP地址, 这通常是一个比较可靠的信息来源。 以下以x_forworded_for例子加以说明,正常情况下,这个值变化过程。
image

HTTP_VIA()
列出中间网关或代理服务器地址,通信协议。当客户端请求到达第一个代理服务器时,该服务器会在自己发出的请求里面添加 Via 头部,并填上自己的相关信息,当下一个代理服务器 收到第一个代理服务器的请求时,会在自己发出的请求里面复制前一个代理服务器的请求的Via 头部,并把自己的相关信息加到后面, 以此类推,当收到最后一个代理服务器的请求时,检查 Via头部,就知道该请求所经过的路由。
例如:

1、Via:1.0 236-81.D07071953.sina.com.cn:80 (squid/2.6.STABLE13)

2、假设来自印度的任何用户从美国的代理服务器访问四川大学网站。
下面我列出了虚拟IP地址的用户流量。

用户(印度1.25.32.51)>>代理(美国54.58.210.255)>> http://scu.edu.cn

根据上面的情况,REMOTE_ADDR将填充54.58.210.255,HTTP_VIA填充54.58.210.255,HTTP_X_FORWARDED_FOR填充1.25.32.51,54.58.210.255

1
2
3
$_SERVER['REMOTE_ADDR']; //访问端(有可能是用户,有可能是代理的)IP
$_SERVER['HTTP_CLIENT_IP']; //有可能存在,可伪造
$_SERVER['HTTP_X_FORWARDED_FOR']; //用户是在哪个IP使用的代理(有可能存在,也可以伪造)

下面比较REMOTE_ADDR、HTTP_VIA、HTTP_X_FORWARDED_FOR三个header头

三个值区别如下:

一、没有使用代理服务器的情况:

REMOTE_ADDR = 您的 IP

HTTP_VIA = 没数值或不显示

HTTP_X_FORWARDED_FOR = 没数值或不显示

二、使用透明代理服务器的情况

REMOTE_ADDR = 最后一个代理服务器 IP

HTTP_VIA = 代理服务器 IP

HTTP_X_FORWARDED_FOR = 您的真实IP,经过多个代理服务器时,这个值类似如下:203.98.182.163, 203.98.182.163, 203.129.72.215。

这类代理服务器还是将您的信息转发给您的访问对象,无法达到隐藏真实身份的目的。

三、使用普通匿名代理服务器的情况

REMOTE_ADDR = 最后一个代理服务器 IP

HTTP_VIA = 代理服务器 IP

HTTP_X_FORWARDED_FOR = 代理服务器 IP ,经过多个代理服务器时,这个值类似如下:203.98.182.163, 203.98.182.163, 203.129.72.215。

隐藏了您的真实IP,但是向访问对象透露了您是使用代理服务器访问他们的。

四、使用欺骗性代理服务器的情况

REMOTE_ADDR = 代理服务器 IP

HTTP_VIA = 代理服务器 IP

HTTP_X_FORWARDED_FOR = 随机的 IP ,经过多个代理服务器时,这个值类似如下:203.98.182.163, 203.98.182.163, 203.129.72.215。

告诉了访问对象您使用了代理服务器,但编造了一个虚假的随机IP代替您的真实IP欺骗它。

五、使用高匿名代理服务器的情况

REMOTE_ADDR = 代理服务器 IP

HTTP_VIA = 没数值或不显示

HTTP_X_FORWARDED_FOR = 没数值或不显示

完全用代理服务器的信息替代了您的所有信息,就象您就是完全使用那台代理服务器直接访问对象。


一个小点:

前面提到的

1
2
$_SERVER['HTTP_CLIENT_IP']; //可以伪造
$_SERVER['HTTP_X_FORWARDED_FOR']; //可以伪造

那么对于一些利用这两个值获取用户IP 的地方,就存在通过修改这两个值来进行xss攻击的可能。

3.Python中使用MongoEngine操作MongoDB

一个Python项目要用到MongoDB,先开始在写入库时还是使用的最原始的语句

1
2
3
4
ip = connection['ip']
ip_set = ip.ip #使用ip库的ip集合
post = {"ip": row[0], "source": row[2], "type": row[3], "created_at": row[4]}
ip_set.insert_one(post)

很是麻烦对吧!后来师兄告诉了有MongoEngine这样一个东西,所以就去看了一下手册,学了一下怎么用。

MongoEngine是一个对象文档映射器(ODM),相当于一个基于SQL的对象关系映射器(ORM),
MongoEngine提供的抽象是基于类的,创建的所有模型都是类。

安装

1
2
pip install pymongo #依赖库
pip install mongoengine

基础使用

使用时先声明一个继承自MongoEngine.Document的类

在类中声明一些属性,相当于创建一个用来保存数据的数据结构,即数据以类似数据结构的形式存入数据库中

1
2
3
4
5
6
7
8
9
10
11
12
13
from mongoengine import *
# 连接数据库
connect('ip') # 连接本地ip数据库/或者没有的话创建新的数据库
# 如需验证和指定主机名
# connect('ip', host='xxx.xxx.xxx.xxx', username='root', password='*****')

#定义一个类,其中类名就是我们的数据库中的集合名
class Ip(Document):
#键值对中的键名
ip = StringField(max_length=30, required=True)#如果required为True则必须赋予初始值,如果有default,赋予初始值则使用默认值
source = StringField(max_length=30, required=True)
created_at = StringField(max_length=30, required=True)
type = StringField(max_length=30, required=True)

field types:

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
BinaryField
BooleanField
ComplexDateTimeField
DateTimeField
DecimalField
DictField
DynamicField
EmailField
EmbeddedDocumentField
EmbeddedDocumentListField
FileField
FloatField
GenericEmbeddedDocumentField
GenericReferenceField
GeoPointField
ImageField
IntField
ListField
MapField
ObjectIdField
ReferenceField
SequenceField
SortedListField
StringField
URLField
UUIDField
PointField
LineStringField
PolygonField
MultiPointField
MultiLineStringField
MultiPolygonField

字段参数:
required

default

unique (默认:False)
当为True时,集合中的任何文档都不会具有相同的值。

primary_key (默认:False)
如果为True,则使用此字段作为集合的主键。

。。。。。。

插入

1
2
cate = Ip(ip="xxx.xxx.xxx.xxx", source="http://www.xxx.com", created_at="2017-11-30", type="xxx")
cate.save() # 保存到数据库

查询和更新

文档类有一个 objects 属性.我们使用它来查询数据库.

1
2
3
4
5
6
7
8
# 返回集合里的所有文档对象的列表
cate = Ip.objects.all()

# 返回所有符合查询条件的结果的文档对象列表
cate = Ip.objects(type="xxx")
# 更新查询到的文档:
cate.type = "yyy"
cate.update()

删除

1
ip.objects(ip="xxx.xxx.xxx.xxx", source="http://www.xxx.com", created_at="2017-11-30", type="xxx").delete()

其他

1
2
#查询结果转换成字典
ips_dict =ip.objects().to_mongo()

举例

查询10=<年龄<30的,按姓名排列

1
2
3
4
5
6
7
8
9
from mongoengine import *
connect('test', host='localhost', port=27017)
import datetime
class Users(Document):
name = StringField(required=True, max_length=200)
age = IntField(required=True)
user_search = Users.objects(age__gte=10, age__lt=33).order_by('name')
for u in user_search:
print("name:",u.name,",age:",u.age)

如果需要按姓名倒序排列,就使用
user_search = Users.objects(age__gte=10, age__lt=33).order_by('-name')