Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
xxadev
jenkins
提交
5cf0a77d
J
jenkins
项目概览
xxadev
/
jenkins
与 Fork 源项目一致
从无法访问的项目Fork
通知
3
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
J
jenkins
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
5cf0a77d
编写于
4月 25, 2018
作者:
W
Wadeck Follonier
提交者:
Daniel Beck
4月 25, 2018
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
[SECURITY-788]
上级
7c5b41bf
变更
4
隐藏空白更改
内联
并排
Showing
4 changed file
with
175 addition
and
13 deletion
+175
-13
core/src/main/java/hudson/FilePath.java
core/src/main/java/hudson/FilePath.java
+15
-4
core/src/main/java/jenkins/SoloFilePathFilter.java
core/src/main/java/jenkins/SoloFilePathFilter.java
+13
-7
core/src/test/java/hudson/FilePathTest.java
core/src/test/java/hudson/FilePathTest.java
+2
-2
test/src/test/java/jenkins/security/s2m/AdminFilePathFilterTest.java
...st/java/jenkins/security/s2m/AdminFilePathFilterTest.java
+145
-0
未找到文件。
core/src/main/java/hudson/FilePath.java
浏览文件 @
5cf0a77d
...
@@ -214,9 +214,14 @@ public final class FilePath implements Serializable {
...
@@ -214,9 +214,14 @@ public final class FilePath implements Serializable {
* This is used to determine whether we are running on the master or the agent.
* This is used to determine whether we are running on the master or the agent.
*/
*/
private
transient
VirtualChannel
channel
;
private
transient
VirtualChannel
channel
;
// since the platform of the agent might be different, can't use java.io.File
/**
private
final
String
remote
;
* Represent the path to the file in the master or the agent
* Since the platform of the agent might be different, can't use java.io.File
*
* The field could not be final since it's modified in {@link #readResolve()}
*/
private
/*final*/
String
remote
;
/**
/**
* If this {@link FilePath} is deserialized to handle file access request from a remote computer,
* If this {@link FilePath} is deserialized to handle file access request from a remote computer,
...
@@ -264,6 +269,11 @@ public final class FilePath implements Serializable {
...
@@ -264,6 +269,11 @@ public final class FilePath implements Serializable {
this
.
remote
=
normalize
(
resolvePathIfRelative
(
base
,
rel
));
this
.
remote
=
normalize
(
resolvePathIfRelative
(
base
,
rel
));
}
}
private
Object
readResolve
()
{
this
.
remote
=
normalize
(
this
.
remote
);
return
this
;
}
private
String
resolvePathIfRelative
(
@Nonnull
FilePath
base
,
@Nonnull
String
rel
)
{
private
String
resolvePathIfRelative
(
@Nonnull
FilePath
base
,
@Nonnull
String
rel
)
{
if
(
isAbsolute
(
rel
))
return
rel
;
if
(
isAbsolute
(
rel
))
return
rel
;
if
(
base
.
isUnix
())
{
if
(
base
.
isUnix
())
{
...
@@ -291,7 +301,8 @@ public final class FilePath implements Serializable {
...
@@ -291,7 +301,8 @@ public final class FilePath implements Serializable {
* {@link File#getParent()} etc cannot handle ".." and "." in the path component very well,
* {@link File#getParent()} etc cannot handle ".." and "." in the path component very well,
* so remove them.
* so remove them.
*/
*/
private
static
String
normalize
(
@Nonnull
String
path
)
{
@Restricted
(
NoExternalUse
.
class
)
public
static
String
normalize
(
@Nonnull
String
path
)
{
StringBuilder
buf
=
new
StringBuilder
();
StringBuilder
buf
=
new
StringBuilder
();
// Check for prefix designating absolute path
// Check for prefix designating absolute path
Matcher
m
=
ABSOLUTE_PREFIX_PATTERN
.
matcher
(
path
);
Matcher
m
=
ABSOLUTE_PREFIX_PATTERN
.
matcher
(
path
);
...
...
core/src/main/java/jenkins/SoloFilePathFilter.java
浏览文件 @
5cf0a77d
package
jenkins
;
package
jenkins
;
import
hudson.FilePath
;
import
javax.annotation.Nullable
;
import
javax.annotation.Nullable
;
import
java.io.File
;
import
java.io.File
;
...
@@ -31,39 +33,43 @@ public final class SoloFilePathFilter extends FilePathFilter {
...
@@ -31,39 +33,43 @@ public final class SoloFilePathFilter extends FilePathFilter {
throw
new
SecurityException
(
"agent may not "
+
op
+
" "
+
f
+
"\nSee https://jenkins.io/redirect/security-144 for more details"
);
throw
new
SecurityException
(
"agent may not "
+
op
+
" "
+
f
+
"\nSee https://jenkins.io/redirect/security-144 for more details"
);
return
true
;
return
true
;
}
}
private
File
normalize
(
File
file
){
return
new
File
(
FilePath
.
normalize
(
file
.
getAbsolutePath
()));
}
@Override
@Override
public
boolean
read
(
File
f
)
throws
SecurityException
{
public
boolean
read
(
File
f
)
throws
SecurityException
{
return
noFalse
(
"read"
,
f
,
base
.
read
(
f
));
return
noFalse
(
"read"
,
f
,
base
.
read
(
normalize
(
f
)
));
}
}
@Override
@Override
public
boolean
write
(
File
f
)
throws
SecurityException
{
public
boolean
write
(
File
f
)
throws
SecurityException
{
return
noFalse
(
"write"
,
f
,
base
.
write
(
f
));
return
noFalse
(
"write"
,
f
,
base
.
write
(
normalize
(
f
)
));
}
}
@Override
@Override
public
boolean
symlink
(
File
f
)
throws
SecurityException
{
public
boolean
symlink
(
File
f
)
throws
SecurityException
{
return
noFalse
(
"symlink"
,
f
,
base
.
write
(
f
));
return
noFalse
(
"symlink"
,
f
,
base
.
write
(
normalize
(
f
)
));
}
}
@Override
@Override
public
boolean
mkdirs
(
File
f
)
throws
SecurityException
{
public
boolean
mkdirs
(
File
f
)
throws
SecurityException
{
return
noFalse
(
"mkdirs"
,
f
,
base
.
mkdirs
(
f
));
return
noFalse
(
"mkdirs"
,
f
,
base
.
mkdirs
(
normalize
(
f
)
));
}
}
@Override
@Override
public
boolean
create
(
File
f
)
throws
SecurityException
{
public
boolean
create
(
File
f
)
throws
SecurityException
{
return
noFalse
(
"create"
,
f
,
base
.
create
(
f
));
return
noFalse
(
"create"
,
f
,
base
.
create
(
normalize
(
f
)
));
}
}
@Override
@Override
public
boolean
delete
(
File
f
)
throws
SecurityException
{
public
boolean
delete
(
File
f
)
throws
SecurityException
{
return
noFalse
(
"delete"
,
f
,
base
.
delete
(
f
));
return
noFalse
(
"delete"
,
f
,
base
.
delete
(
normalize
(
f
)
));
}
}
@Override
@Override
public
boolean
stat
(
File
f
)
throws
SecurityException
{
public
boolean
stat
(
File
f
)
throws
SecurityException
{
return
noFalse
(
"stat"
,
f
,
base
.
stat
(
f
));
return
noFalse
(
"stat"
,
f
,
base
.
stat
(
normalize
(
f
)
));
}
}
}
}
core/src/test/java/hudson/FilePathTest.java
浏览文件 @
5cf0a77d
...
@@ -599,7 +599,7 @@ public class FilePathTest {
...
@@ -599,7 +599,7 @@ public class FilePathTest {
when
(
con
.
getResponseCode
())
when
(
con
.
getResponseCode
())
.
thenReturn
(
HttpURLConnection
.
HTTP_NOT_MODIFIED
);
.
thenReturn
(
HttpURLConnection
.
HTTP_NOT_MODIFIED
);
assertFalse
(
d
.
installIfNecessaryFrom
(
url
,
null
,
null
));
assertFalse
(
d
.
installIfNecessaryFrom
(
url
,
null
,
"message if failed"
));
verify
(
con
).
setIfModifiedSince
(
123000
);
verify
(
con
).
setIfModifiedSince
(
123000
);
}
}
...
@@ -618,7 +618,7 @@ public class FilePathTest {
...
@@ -618,7 +618,7 @@ public class FilePathTest {
when
(
con
.
getInputStream
())
when
(
con
.
getInputStream
())
.
thenReturn
(
someZippedContent
());
.
thenReturn
(
someZippedContent
());
assertTrue
(
d
.
installIfNecessaryFrom
(
url
,
null
,
null
));
assertTrue
(
d
.
installIfNecessaryFrom
(
url
,
null
,
"message if failed"
));
}
}
@Issue
(
"JENKINS-26196"
)
@Issue
(
"JENKINS-26196"
)
...
...
test/src/test/java/jenkins/security/s2m/AdminFilePathFilterTest.java
浏览文件 @
5cf0a77d
...
@@ -25,8 +25,17 @@
...
@@ -25,8 +25,17 @@
package
jenkins.security.s2m
;
package
jenkins.security.s2m
;
import
java.io.File
;
import
java.io.File
;
import
java.io.IOException
;
import
java.io.PrintWriter
;
import
java.io.StringWriter
;
import
java.lang.reflect.Field
;
import
javax.inject.Inject
;
import
javax.inject.Inject
;
import
static
org
.
junit
.
Assert
.*;
import
static
org
.
junit
.
Assert
.*;
import
hudson.FilePath
;
import
hudson.model.Slave
;
import
hudson.remoting.Callable
;
import
org.jenkinsci.remoting.RoleChecker
;
import
org.junit.Before
;
import
org.junit.Before
;
import
org.junit.Rule
;
import
org.junit.Rule
;
import
org.junit.Test
;
import
org.junit.Test
;
...
@@ -57,5 +66,141 @@ public class AdminFilePathFilterTest {
...
@@ -57,5 +66,141 @@ public class AdminFilePathFilterTest {
assertFalse
(
rule
.
checkFileAccess
(
"write"
,
new
File
(
buildDir
,
"program.dat"
)));
assertFalse
(
rule
.
checkFileAccess
(
"write"
,
new
File
(
buildDir
,
"program.dat"
)));
assertFalse
(
rule
.
checkFileAccess
(
"write"
,
new
File
(
buildDir
,
"workflow/23.xml"
)));
assertFalse
(
rule
.
checkFileAccess
(
"write"
,
new
File
(
buildDir
,
"workflow/23.xml"
)));
}
}
@Test
public
void
slaveCannotReadFileFromSecrets_butCanFromUserContent
()
throws
Exception
{
Slave
s
=
r
.
createOnlineSlave
();
FilePath
root
=
r
.
jenkins
.
getRootPath
();
{
// agent can read userContent folder
FilePath
rootUserContentFolder
=
root
.
child
(
"userContent"
);
FilePath
rootTargetPublic
=
rootUserContentFolder
.
child
(
"target_public.txt"
);
rootTargetPublic
.
write
(
"target_public"
,
null
);
checkSlave_can_readFile
(
s
,
rootTargetPublic
);
}
{
// agent cannot read files inside secrets
FilePath
rootSecretFolder
=
root
.
child
(
"secrets"
);
FilePath
rootTargetPrivate
=
rootSecretFolder
.
child
(
"target_private.txt"
);
rootTargetPrivate
.
write
(
"target_private"
,
null
);
checkSlave_cannot_readFile
(
s
,
rootTargetPrivate
);
}
rule
.
setMasterKillSwitch
(
true
);
{
// with the master kill switch activated, agent can read files inside secrets
FilePath
rootSecretFolder
=
root
.
child
(
"secrets"
);
FilePath
rootTargetPrivate
=
rootSecretFolder
.
child
(
"target_private.txt"
);
checkSlave_can_readFile
(
s
,
rootTargetPrivate
);
}
}
private
static
class
ReadFileS2MCallable
implements
Callable
<
String
,
Exception
>
{
private
final
FilePath
p
;
ReadFileS2MCallable
(
FilePath
p
)
{
this
.
p
=
p
;
}
@Override
public
String
call
()
throws
Exception
{
assertTrue
(
p
.
isRemote
());
return
p
.
readToString
();
}
@Override
public
void
checkRoles
(
RoleChecker
checker
)
throws
SecurityException
{
// simulate legacy Callable impls
throw
new
NoSuchMethodError
();
}
}
@Test
@Issue
(
"SECURITY-788"
)
public
void
slaveCannotUse_dotDotSlashStuff_toBypassRestriction
()
throws
Exception
{
Slave
s
=
r
.
createOnlineSlave
();
FilePath
root
=
r
.
jenkins
.
getRootPath
();
{
// use ../ to access a non-restricted folder
FilePath
rootUserContentFolder
=
root
.
child
(
"userContent"
);
FilePath
rootTargetPublic
=
rootUserContentFolder
.
child
(
"target_public.txt"
);
rootTargetPublic
.
write
(
"target_public"
,
null
);
FilePath
dotDotSlashTargetPublic
=
root
.
child
(
"logs/target_public.txt"
);
replaceRemote
(
dotDotSlashTargetPublic
,
"logs"
,
"logs/../userContent"
);
checkSlave_can_readFile
(
s
,
dotDotSlashTargetPublic
);
}
{
// use ../ to try to bypass the rules
FilePath
rootSecretFolder
=
root
.
child
(
"secrets"
);
FilePath
rootTargetPrivate
=
rootSecretFolder
.
child
(
"target_private.txt"
);
rootTargetPrivate
.
write
(
"target_private"
,
null
);
FilePath
dotDotSlashTargetPrivate
=
root
.
child
(
"userContent/target_private.txt"
);
replaceRemote
(
dotDotSlashTargetPrivate
,
"userContent"
,
"userContent/../secrets"
);
checkSlave_cannot_readFile
(
s
,
dotDotSlashTargetPrivate
);
}
}
@Test
@Issue
(
"SECURITY-788"
)
public
void
slaveCannotUse_encodedCharacters_toBypassRestriction
()
throws
Exception
{
Slave
s
=
r
.
createOnlineSlave
();
FilePath
root
=
r
.
jenkins
.
getRootPath
();
// \u002e is the Unicode of . and is interpreted directly by Java as .
{
// use ../ to access a non-restricted folder
FilePath
rootUserContentFolder
=
root
.
child
(
"userContent"
);
FilePath
rootTargetPublic
=
rootUserContentFolder
.
child
(
"target_public.txt"
);
rootTargetPublic
.
write
(
"target_public"
,
null
);
FilePath
dotDotSlashTargetPublic
=
root
.
child
(
"logs/target_public.txt"
);
replaceRemote
(
dotDotSlashTargetPublic
,
"logs"
,
"logs/\u002e\u002e/userContent"
);
checkSlave_can_readFile
(
s
,
dotDotSlashTargetPublic
);
}
{
// use ../ to try to bypass the rules
FilePath
rootSecretFolder
=
root
.
child
(
"secrets"
);
FilePath
rootTargetPrivate
=
rootSecretFolder
.
child
(
"target_private.txt"
);
rootTargetPrivate
.
write
(
"target_private"
,
null
);
FilePath
dotDotSlashTargetPrivate
=
root
.
child
(
"userContent/target_private.txt"
);
replaceRemote
(
dotDotSlashTargetPrivate
,
"userContent"
,
"userContent/\u002e\u002e/secrets"
);
checkSlave_cannot_readFile
(
s
,
dotDotSlashTargetPrivate
);
}
}
private
void
checkSlave_can_readFile
(
Slave
s
,
FilePath
target
)
throws
Exception
{
// slave can read file from userContent
String
content
=
s
.
getChannel
().
call
(
new
ReadFileS2MCallable
(
target
));
// and the master can directly reach it
assertEquals
(
target
.
readToString
(),
content
);
}
private
void
checkSlave_cannot_readFile
(
Slave
s
,
FilePath
target
)
throws
Exception
{
try
{
s
.
getChannel
().
call
(
new
ReadFileS2MCallable
(
target
));
fail
(
"Slave should not be able to read file in "
+
target
.
getRemote
());
}
catch
(
IOException
e
){
Throwable
t
=
e
.
getCause
();
assertTrue
(
t
instanceof
SecurityException
);
SecurityException
se
=
(
SecurityException
)
t
;
StringWriter
sw
=
new
StringWriter
();
se
.
printStackTrace
(
new
PrintWriter
(
sw
));
assertTrue
(
sw
.
toString
().
contains
(
"agent may not read"
));
}
}
// to bypass the normalization done in constructor
private
void
replaceRemote
(
FilePath
p
,
String
before
,
String
after
)
throws
Exception
{
Field
field
=
FilePath
.
class
.
getDeclaredField
(
"remote"
);
field
.
setAccessible
(
true
);
String
currentRemote
=
(
String
)
field
.
get
(
p
);
String
newRemote
=
currentRemote
.
replace
(
before
,
after
);
field
.
set
(
p
,
newRemote
);
}
}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录