Fossil SCM

(cherry-pick): Fix the SSH sync protocol to avoid "ssh" command-line option injection attacks such as those fixed in Git 2.14.1, Mercurial 4.2.3, and Subversion 1.9.7. As "ssh://" URLs cannot be buried out of sight in Fossil, the vulnerability does not appear to be as severe as in those other systems. (cherry-pick): Enhance the ssh:// URL to be cautious about the fossil= query parameter. Only commands "fossil" and "echo" (with an optional path) are accepted.

jan.nijtmans 2017-08-22 09:44 UTC branch-2.3
Commit 1f18d23d760c37c78310188ae1db3b9d6e7a4b93d6537df99c2ba3d57075ee45
--- src/http_transport.c
+++ src/http_transport.c
@@ -73,10 +73,25 @@
7373
if( resetFlag ){
7474
transport.nSent = 0;
7575
transport.nRcvd = 0;
7676
}
7777
}
78
+
79
+/*
80
+** Check zFossil to see if it is a reasonable "fossil" command to
81
+** run on the server. Do not allow an attacker to substitute something
82
+** like "/bin/rm".
83
+*/
84
+static int is_safe_fossil_command(const char *zFossil){
85
+ static const char *azSafe[] = { "*/fossil", "*/echo" };
86
+ int i;
87
+ for(i=0; i<sizeof(azSafe)/sizeof(azSafe[0]); i++){
88
+ if( sqlite3_strglob(azSafe[i], zFossil)==0 ) return 1;
89
+ if( strcmp(azSafe[i]+2, zFossil)==0 ) return 1;
90
+ }
91
+ return 0;
92
+}
7893
7994
/*
8095
** Default SSH command
8196
*/
8297
#ifdef _WIN32
@@ -93,11 +108,10 @@
93108
** to talk to the remote machine.
94109
*/
95110
char *zSsh; /* The base SSH command */
96111
Blob zCmd; /* The SSH command */
97112
char *zHost; /* The host name to contact */
98
- int n; /* Size of prefix string */
99113
100114
socket_ssh_resolve_addr(pUrlData);
101115
zSsh = db_get("ssh-command", zDefaultSshCmd);
102116
blob_init(&zCmd, zSsh, -1);
103117
if( pUrlData->port!=pUrlData->dfltPort && pUrlData->port ){
@@ -105,33 +119,31 @@
105119
blob_appendf(&zCmd, " -P %d", pUrlData->port);
106120
#else
107121
blob_appendf(&zCmd, " -p %d", pUrlData->port);
108122
#endif
109123
}
110
- if( g.fSshTrace ){
111
- fossil_force_newline();
112
- fossil_print("%s", blob_str(&zCmd)); /* Show the base of the SSH command */
113
- }
114124
if( pUrlData->user && pUrlData->user[0] ){
115125
zHost = mprintf("%s@%s", pUrlData->user, pUrlData->name);
126
+ blob_append_escaped_arg(&zCmd, zHost);
127
+ fossil_free(zHost);
116128
}else{
117
- zHost = mprintf("%s", pUrlData->name);
129
+ blob_append_escaped_arg(&zCmd, pUrlData->name);
130
+ }
131
+ if( !is_safe_fossil_command(pUrlData->fossil) ){
132
+ fossil_fatal("the ssh:// URL is asking to run an unsafe command [%s] on "
133
+ "the server.", pUrlData->fossil);
118134
}
119
- n = blob_size(&zCmd);
120
- blob_append(&zCmd, " ", 1);
121
- shell_escape(&zCmd, zHost);
122
- blob_append(&zCmd, " ", 1);
123
- shell_escape(&zCmd, mprintf("%s", pUrlData->fossil));
135
+ blob_append_escaped_arg(&zCmd, pUrlData->fossil);
124136
blob_append(&zCmd, " test-http", 10);
125137
if( pUrlData->path && pUrlData->path[0] ){
126
- blob_append(&zCmd, " ", 1);
127
- shell_escape(&zCmd, mprintf("%s", pUrlData->path));
138
+ blob_append_escaped_arg(&zCmd, pUrlData->path);
139
+ }else{
140
+ fossil_fatal("ssh:// URI does not specify a path to the repository");
128141
}
129142
if( g.fSshTrace ){
130
- fossil_print("%s\n", blob_str(&zCmd)+n); /* Show tail of SSH command */
143
+ fossil_print("%s\n", blob_str(&zCmd)); /* Show the whole SSH command */
131144
}
132
- free(zHost);
133145
popen2(blob_str(&zCmd), &sshIn, &sshOut, &sshPid);
134146
if( sshPid==0 ){
135147
socket_set_errmsg("cannot start ssh tunnel using [%b]", &zCmd);
136148
}
137149
blob_reset(&zCmd);
138150
--- src/http_transport.c
+++ src/http_transport.c
@@ -73,10 +73,25 @@
73 if( resetFlag ){
74 transport.nSent = 0;
75 transport.nRcvd = 0;
76 }
77 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
79 /*
80 ** Default SSH command
81 */
82 #ifdef _WIN32
@@ -93,11 +108,10 @@
93 ** to talk to the remote machine.
94 */
95 char *zSsh; /* The base SSH command */
96 Blob zCmd; /* The SSH command */
97 char *zHost; /* The host name to contact */
98 int n; /* Size of prefix string */
99
100 socket_ssh_resolve_addr(pUrlData);
101 zSsh = db_get("ssh-command", zDefaultSshCmd);
102 blob_init(&zCmd, zSsh, -1);
103 if( pUrlData->port!=pUrlData->dfltPort && pUrlData->port ){
@@ -105,33 +119,31 @@
105 blob_appendf(&zCmd, " -P %d", pUrlData->port);
106 #else
107 blob_appendf(&zCmd, " -p %d", pUrlData->port);
108 #endif
109 }
110 if( g.fSshTrace ){
111 fossil_force_newline();
112 fossil_print("%s", blob_str(&zCmd)); /* Show the base of the SSH command */
113 }
114 if( pUrlData->user && pUrlData->user[0] ){
115 zHost = mprintf("%s@%s", pUrlData->user, pUrlData->name);
 
 
116 }else{
117 zHost = mprintf("%s", pUrlData->name);
 
 
 
 
118 }
119 n = blob_size(&zCmd);
120 blob_append(&zCmd, " ", 1);
121 shell_escape(&zCmd, zHost);
122 blob_append(&zCmd, " ", 1);
123 shell_escape(&zCmd, mprintf("%s", pUrlData->fossil));
124 blob_append(&zCmd, " test-http", 10);
125 if( pUrlData->path && pUrlData->path[0] ){
126 blob_append(&zCmd, " ", 1);
127 shell_escape(&zCmd, mprintf("%s", pUrlData->path));
 
128 }
129 if( g.fSshTrace ){
130 fossil_print("%s\n", blob_str(&zCmd)+n); /* Show tail of SSH command */
131 }
132 free(zHost);
133 popen2(blob_str(&zCmd), &sshIn, &sshOut, &sshPid);
134 if( sshPid==0 ){
135 socket_set_errmsg("cannot start ssh tunnel using [%b]", &zCmd);
136 }
137 blob_reset(&zCmd);
138
--- src/http_transport.c
+++ src/http_transport.c
@@ -73,10 +73,25 @@
73 if( resetFlag ){
74 transport.nSent = 0;
75 transport.nRcvd = 0;
76 }
77 }
78
79 /*
80 ** Check zFossil to see if it is a reasonable "fossil" command to
81 ** run on the server. Do not allow an attacker to substitute something
82 ** like "/bin/rm".
83 */
84 static int is_safe_fossil_command(const char *zFossil){
85 static const char *azSafe[] = { "*/fossil", "*/echo" };
86 int i;
87 for(i=0; i<sizeof(azSafe)/sizeof(azSafe[0]); i++){
88 if( sqlite3_strglob(azSafe[i], zFossil)==0 ) return 1;
89 if( strcmp(azSafe[i]+2, zFossil)==0 ) return 1;
90 }
91 return 0;
92 }
93
94 /*
95 ** Default SSH command
96 */
97 #ifdef _WIN32
@@ -93,11 +108,10 @@
108 ** to talk to the remote machine.
109 */
110 char *zSsh; /* The base SSH command */
111 Blob zCmd; /* The SSH command */
112 char *zHost; /* The host name to contact */
 
113
114 socket_ssh_resolve_addr(pUrlData);
115 zSsh = db_get("ssh-command", zDefaultSshCmd);
116 blob_init(&zCmd, zSsh, -1);
117 if( pUrlData->port!=pUrlData->dfltPort && pUrlData->port ){
@@ -105,33 +119,31 @@
119 blob_appendf(&zCmd, " -P %d", pUrlData->port);
120 #else
121 blob_appendf(&zCmd, " -p %d", pUrlData->port);
122 #endif
123 }
 
 
 
 
124 if( pUrlData->user && pUrlData->user[0] ){
125 zHost = mprintf("%s@%s", pUrlData->user, pUrlData->name);
126 blob_append_escaped_arg(&zCmd, zHost);
127 fossil_free(zHost);
128 }else{
129 blob_append_escaped_arg(&zCmd, pUrlData->name);
130 }
131 if( !is_safe_fossil_command(pUrlData->fossil) ){
132 fossil_fatal("the ssh:// URL is asking to run an unsafe command [%s] on "
133 "the server.", pUrlData->fossil);
134 }
135 blob_append_escaped_arg(&zCmd, pUrlData->fossil);
 
 
 
 
136 blob_append(&zCmd, " test-http", 10);
137 if( pUrlData->path && pUrlData->path[0] ){
138 blob_append_escaped_arg(&zCmd, pUrlData->path);
139 }else{
140 fossil_fatal("ssh:// URI does not specify a path to the repository");
141 }
142 if( g.fSshTrace ){
143 fossil_print("%s\n", blob_str(&zCmd)); /* Show the whole SSH command */
144 }
 
145 popen2(blob_str(&zCmd), &sshIn, &sshOut, &sshPid);
146 if( sshPid==0 ){
147 socket_set_errmsg("cannot start ssh tunnel using [%b]", &zCmd);
148 }
149 blob_reset(&zCmd);
150
--- src/http_transport.c
+++ src/http_transport.c
@@ -73,10 +73,25 @@
7373
if( resetFlag ){
7474
transport.nSent = 0;
7575
transport.nRcvd = 0;
7676
}
7777
}
78
+
79
+/*
80
+** Check zFossil to see if it is a reasonable "fossil" command to
81
+** run on the server. Do not allow an attacker to substitute something
82
+** like "/bin/rm".
83
+*/
84
+static int is_safe_fossil_command(const char *zFossil){
85
+ static const char *azSafe[] = { "*/fossil", "*/echo" };
86
+ int i;
87
+ for(i=0; i<sizeof(azSafe)/sizeof(azSafe[0]); i++){
88
+ if( sqlite3_strglob(azSafe[i], zFossil)==0 ) return 1;
89
+ if( strcmp(azSafe[i]+2, zFossil)==0 ) return 1;
90
+ }
91
+ return 0;
92
+}
7893
7994
/*
8095
** Default SSH command
8196
*/
8297
#ifdef _WIN32
@@ -93,11 +108,10 @@
93108
** to talk to the remote machine.
94109
*/
95110
char *zSsh; /* The base SSH command */
96111
Blob zCmd; /* The SSH command */
97112
char *zHost; /* The host name to contact */
98
- int n; /* Size of prefix string */
99113
100114
socket_ssh_resolve_addr(pUrlData);
101115
zSsh = db_get("ssh-command", zDefaultSshCmd);
102116
blob_init(&zCmd, zSsh, -1);
103117
if( pUrlData->port!=pUrlData->dfltPort && pUrlData->port ){
@@ -105,33 +119,31 @@
105119
blob_appendf(&zCmd, " -P %d", pUrlData->port);
106120
#else
107121
blob_appendf(&zCmd, " -p %d", pUrlData->port);
108122
#endif
109123
}
110
- if( g.fSshTrace ){
111
- fossil_force_newline();
112
- fossil_print("%s", blob_str(&zCmd)); /* Show the base of the SSH command */
113
- }
114124
if( pUrlData->user && pUrlData->user[0] ){
115125
zHost = mprintf("%s@%s", pUrlData->user, pUrlData->name);
126
+ blob_append_escaped_arg(&zCmd, zHost);
127
+ fossil_free(zHost);
116128
}else{
117
- zHost = mprintf("%s", pUrlData->name);
129
+ blob_append_escaped_arg(&zCmd, pUrlData->name);
130
+ }
131
+ if( !is_safe_fossil_command(pUrlData->fossil) ){
132
+ fossil_fatal("the ssh:// URL is asking to run an unsafe command [%s] on "
133
+ "the server.", pUrlData->fossil);
118134
}
119
- n = blob_size(&zCmd);
120
- blob_append(&zCmd, " ", 1);
121
- shell_escape(&zCmd, zHost);
122
- blob_append(&zCmd, " ", 1);
123
- shell_escape(&zCmd, mprintf("%s", pUrlData->fossil));
135
+ blob_append_escaped_arg(&zCmd, pUrlData->fossil);
124136
blob_append(&zCmd, " test-http", 10);
125137
if( pUrlData->path && pUrlData->path[0] ){
126
- blob_append(&zCmd, " ", 1);
127
- shell_escape(&zCmd, mprintf("%s", pUrlData->path));
138
+ blob_append_escaped_arg(&zCmd, pUrlData->path);
139
+ }else{
140
+ fossil_fatal("ssh:// URI does not specify a path to the repository");
128141
}
129142
if( g.fSshTrace ){
130
- fossil_print("%s\n", blob_str(&zCmd)+n); /* Show tail of SSH command */
143
+ fossil_print("%s\n", blob_str(&zCmd)); /* Show the whole SSH command */
131144
}
132
- free(zHost);
133145
popen2(blob_str(&zCmd), &sshIn, &sshOut, &sshPid);
134146
if( sshPid==0 ){
135147
socket_set_errmsg("cannot start ssh tunnel using [%b]", &zCmd);
136148
}
137149
blob_reset(&zCmd);
138150
--- src/http_transport.c
+++ src/http_transport.c
@@ -73,10 +73,25 @@
73 if( resetFlag ){
74 transport.nSent = 0;
75 transport.nRcvd = 0;
76 }
77 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
79 /*
80 ** Default SSH command
81 */
82 #ifdef _WIN32
@@ -93,11 +108,10 @@
93 ** to talk to the remote machine.
94 */
95 char *zSsh; /* The base SSH command */
96 Blob zCmd; /* The SSH command */
97 char *zHost; /* The host name to contact */
98 int n; /* Size of prefix string */
99
100 socket_ssh_resolve_addr(pUrlData);
101 zSsh = db_get("ssh-command", zDefaultSshCmd);
102 blob_init(&zCmd, zSsh, -1);
103 if( pUrlData->port!=pUrlData->dfltPort && pUrlData->port ){
@@ -105,33 +119,31 @@
105 blob_appendf(&zCmd, " -P %d", pUrlData->port);
106 #else
107 blob_appendf(&zCmd, " -p %d", pUrlData->port);
108 #endif
109 }
110 if( g.fSshTrace ){
111 fossil_force_newline();
112 fossil_print("%s", blob_str(&zCmd)); /* Show the base of the SSH command */
113 }
114 if( pUrlData->user && pUrlData->user[0] ){
115 zHost = mprintf("%s@%s", pUrlData->user, pUrlData->name);
 
 
116 }else{
117 zHost = mprintf("%s", pUrlData->name);
 
 
 
 
118 }
119 n = blob_size(&zCmd);
120 blob_append(&zCmd, " ", 1);
121 shell_escape(&zCmd, zHost);
122 blob_append(&zCmd, " ", 1);
123 shell_escape(&zCmd, mprintf("%s", pUrlData->fossil));
124 blob_append(&zCmd, " test-http", 10);
125 if( pUrlData->path && pUrlData->path[0] ){
126 blob_append(&zCmd, " ", 1);
127 shell_escape(&zCmd, mprintf("%s", pUrlData->path));
 
128 }
129 if( g.fSshTrace ){
130 fossil_print("%s\n", blob_str(&zCmd)+n); /* Show tail of SSH command */
131 }
132 free(zHost);
133 popen2(blob_str(&zCmd), &sshIn, &sshOut, &sshPid);
134 if( sshPid==0 ){
135 socket_set_errmsg("cannot start ssh tunnel using [%b]", &zCmd);
136 }
137 blob_reset(&zCmd);
138
--- src/http_transport.c
+++ src/http_transport.c
@@ -73,10 +73,25 @@
73 if( resetFlag ){
74 transport.nSent = 0;
75 transport.nRcvd = 0;
76 }
77 }
78
79 /*
80 ** Check zFossil to see if it is a reasonable "fossil" command to
81 ** run on the server. Do not allow an attacker to substitute something
82 ** like "/bin/rm".
83 */
84 static int is_safe_fossil_command(const char *zFossil){
85 static const char *azSafe[] = { "*/fossil", "*/echo" };
86 int i;
87 for(i=0; i<sizeof(azSafe)/sizeof(azSafe[0]); i++){
88 if( sqlite3_strglob(azSafe[i], zFossil)==0 ) return 1;
89 if( strcmp(azSafe[i]+2, zFossil)==0 ) return 1;
90 }
91 return 0;
92 }
93
94 /*
95 ** Default SSH command
96 */
97 #ifdef _WIN32
@@ -93,11 +108,10 @@
108 ** to talk to the remote machine.
109 */
110 char *zSsh; /* The base SSH command */
111 Blob zCmd; /* The SSH command */
112 char *zHost; /* The host name to contact */
 
113
114 socket_ssh_resolve_addr(pUrlData);
115 zSsh = db_get("ssh-command", zDefaultSshCmd);
116 blob_init(&zCmd, zSsh, -1);
117 if( pUrlData->port!=pUrlData->dfltPort && pUrlData->port ){
@@ -105,33 +119,31 @@
119 blob_appendf(&zCmd, " -P %d", pUrlData->port);
120 #else
121 blob_appendf(&zCmd, " -p %d", pUrlData->port);
122 #endif
123 }
 
 
 
 
124 if( pUrlData->user && pUrlData->user[0] ){
125 zHost = mprintf("%s@%s", pUrlData->user, pUrlData->name);
126 blob_append_escaped_arg(&zCmd, zHost);
127 fossil_free(zHost);
128 }else{
129 blob_append_escaped_arg(&zCmd, pUrlData->name);
130 }
131 if( !is_safe_fossil_command(pUrlData->fossil) ){
132 fossil_fatal("the ssh:// URL is asking to run an unsafe command [%s] on "
133 "the server.", pUrlData->fossil);
134 }
135 blob_append_escaped_arg(&zCmd, pUrlData->fossil);
 
 
 
 
136 blob_append(&zCmd, " test-http", 10);
137 if( pUrlData->path && pUrlData->path[0] ){
138 blob_append_escaped_arg(&zCmd, pUrlData->path);
139 }else{
140 fossil_fatal("ssh:// URI does not specify a path to the repository");
141 }
142 if( g.fSshTrace ){
143 fossil_print("%s\n", blob_str(&zCmd)); /* Show the whole SSH command */
144 }
 
145 popen2(blob_str(&zCmd), &sshIn, &sshOut, &sshPid);
146 if( sshPid==0 ){
147 socket_set_errmsg("cannot start ssh tunnel using [%b]", &zCmd);
148 }
149 blob_reset(&zCmd);
150

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button