一、综述
恶意网页链接的检测方案有很多
例如http://fsecurify.com/using-machine-learning-detect-malicious-urls/
该文使用了机器学习逻辑回归算法
但是该算法存在一些问题,一个是用TFIDF方法来获取词频,该方法的缺陷就是只能获取单词在整段文字的词频信息,
没办法获取上下文语境的信息
本文从自然语言的角度解析URL链接,恶意链接与文本恰有一些相似之处,所以尝试了自然语言处理的
方法来检测网页
本文将会简单介绍一些算法
二、算法介绍
1)典型的利用CNN进行文本分类的思路
卷积神经网络用于NLP的检测已经有很多实践以及论文支持,
比如http://www.wildml.com/2015/11/understanding-convolutional-neural-networks-for-nlp/
利用CNN横向连接实现文本情感分析,本博文也是基于该原理,实现恶意网页检测。
第一层进行一层低维词嵌入,把单词句子表示成向量形式,比较常用的词嵌入手段是word2vec,
第二层在词向量上进行卷积操作,可以多次使用不同尺寸的filter, 这样每次划过的单词数量就不同,
可以利用该特性自动抽取到上下文之间的关系特征。
第三层进行max-pooling。
2)重新思考URL检测问题
从文本分类上获得启发,能否借鉴它的这种想法,利用到URL上来?
博主把借鉴了这套网络,把它迁到url上来,对URL结构进行了分析。
这里以一条链接举例说明url的低维嵌入方法,请看
https://q.taobao.com/?spm=a21bo.50862.201859.7.spjPF3
一般url分成三部分 : 协议//主机名+域名/参数
三段之间是用" / "分割的,主机名和域名之间又是用"." 分割,参数之间的传递
常用的分割符有"?", "=" ,"&" ,"-" ,"."等
一般钓鱼的链接会在域名和主机名之间作文章,进行一些域名混淆的恶意行为
而恶意用户请求会从请求参数作文章,比如进行恶意SQL注入
博主用了这六个分隔符,实现了url的切割,可以获取到整条url重要字段的信息。
需要注意的是,为了获取各个字段之间的位置关系,
提取的时候不可随意将顺序调换或者随意删除重复字段,
这是本文与综述中提到的文章所用的取词方法极大的不同之处,
本文更强调的是字段与字段之间的关系,同时也兼顾了字段的出现频率。
3)word2vec
有了分割好的字段,就可以进行词向量训练了。
简单描述下word2vec算法的思想
Word2Vec实际上是两种不同的方法:ContinuousBag of Words (CBOW) 和Skip-gram。
CBOW的目标是根据上下文来预测当前词语的概率。
Skip-gram刚好相反:根据当前词语来预测上下文的概率(如图 )
这实际上也是一种联系上下文的特征提取
可以通过调节window来设置上下文的范围,其他还有很多调参细节,这里不再细说
博主主要用它来实现切割好的单词的低维嵌入。
接下来接入前文所描述的卷积结构。
最后实现的结构如图所示
对于字段之间的位置关系的记忆,LSTM也可以做到,但又是基于不同的原理,与本文讨论的思路不同
博主后面有时间可能会尝试对比一下。
三、核心代码
import tensorflow as tf
import numpy as np
class URLCNN(object):
def __init__(
self, sequence_length, num_classes,
embedding_size, filter_sizes, num_filters, l2_reg_lambda=0.0):
# Placeholders for input, output, dropout
self.input_x = tf.placeholder(tf.float32, [None, sequence_length, embedding_size], name="input_x")
self.input_y = tf.placeholder(tf.float32, [None, num_classes], name="input_y")
self.dropout_keep_prob = tf.placeholder(tf.float32, name="dropout_keep_prob")
# Keeping track of l2 regularization loss (optional)
l2_loss = tf.constant(0.0)
# Embedding layer
self.embedded_chars = self.input_x
self.embedded_chars_expended = tf.expand_dims(self.embedded_chars, -1)
# Create a convolution + maxpool layer for each filter size
pooled_outputs = []
for i, filter_size in enumerate(filter_sizes):
with tf.name_scope("conv-maxpool-%s" % filter_size):
# Convolution layer
filter_shape = [filter_size, embedding_size, 1, num_filters]
W = tf.Variable(tf.truncated_normal(filter_shape, stddev=0.1), name="W")
b = tf.Variable(tf.constant(0.1, shape=[num_filters]), name="b")
conv = tf.nn.conv2d(
self.embedded_chars_expended,
W,
strides=[1, 1, 1, 1],
padding="VALID",
name="conv")
# Apply nonlinearity
h = tf.nn.relu(tf.nn.bias_add(conv, b), name="relu")
# Maxpooling over the outputs
pooled = tf.nn.max_pool(
h,
ksize=[1, sequence_length - filter_size + 1, 1, 1],
strides=[1, 1, 1, 1],
padding="VALID",
name="pool")
pooled_outputs.append(pooled)
# Combine all the pooled features
num_filters_total = num_filters * len(filter_sizes)
self.h_pool = tf.concat(pooled_outputs, 3)
self.h_pool_flat = tf.reshape(self.h_pool, [-1, num_filters_total])
# Add Batch Normalization
epsilon = 1e-3
with tf.name_scope("BATCH-NORM"):
batch_mean,batch_var = tf.nn.moments(self.h_pool_flat,[0])
scale = tf.Variable(tf.ones([384]))
beta = tf.Variable(tf.zeros([384]))
self.BN = tf.nn.batch_normalization(self.h_pool_flat,batch_mean,batch_var,beta,scale,epsilon)
# Add 2-layer-MLP
h1_units=128
h2_units=64
with tf.name_scope("FC-Layer-1"):
W = tf.Variable(tf.truncated_normal(shape=[384,h1_units], stddev=0.1), name="W")
b = tf.Variable(tf.constant(0.1, shape=[h1_units]), name="b")
self.hidden_1 = tf.nn.relu(tf.nn.xw_plus_b(self.BN,W,b,name="fc1"))
with tf.name_scope("FC-Layer-2"):
W = tf.Variable(tf.truncated_normal(shape=[h1_units,h2_units], stddev=0.1), name="W")
b = tf.Variable(tf.constant(0.1, shape=[h2_units]), name="b")
self.hidden_2 = tf.nn.relu(tf.nn.xw_plus_b(self.hidden_1,W,b,name="hidden"))
# Final scores and predictions
with tf.name_scope("output"):
W = tf.get_variable(
"W",
# shape=[num_filters_total, num_classes],
shape=[h2_units,num_classes],
initializer=tf.contrib.layers.xavier_initializer())
b = tf.Variable(tf.constant(0.1, shape=[num_classes], name="b"))
l2_loss += tf.nn.l2_loss(W)
l2_loss += tf.nn.l2_loss(b)
self.scores = tf.nn.xw_plus_b(self.hidden_2, W, b, name="scores")
self.predictions = tf.argmax(self.scores, 1, name="predictions")
# Calculate Mean cross-entropy loss
with tf.name_scope("loss"):
losses = tf.nn.softmax_cross_entropy_with_logits(logits=self.scores, labels=self.input_y)
self.loss = tf.reduce_mean(losses) + l2_reg_lambda * l2_loss
# Accuracy
with tf.name_scope("accuracy"):
correct_predictions = tf.equal(self.predictions, tf.argmax(self.input_y, 1))
self.accuracy = tf.reduce_mean(tf.cast(correct_predictions, "float"), name="accuracy")
GitHub : https://github.com/paradise6/DetectMaliciousURL