Vulnerability: It has been discovered that lftp up to and including version 4.8.3 does not properly sanitize remote file names, leading to a loss of integrity on the local system when reverse mirroring is used. A remote attacker may trick a user to use reverse mirroring on an attacker controlled FTP server, resulting in the removal of all files in the current working directory of the victim's system.
Vulnerability ID: CVE-2018-10916
CWE: CWE-20
Severity score: 7.8
Project: https://github.com/lavv17/lftp
Published: 2018-08-01
File: src/MirrorJob.cc
Commit message: mirror: prepend ./ to rm and chmod arguments to avoid URL recognition (fix #452)
Commit author: Alexander V. Lukyanov

Vulnerable version (before fix):
 int   MirrorJob::Do() 	    } 	    continue; 	 } 	 if(script) 	 { 	    ArgV args("rm"); 	    if(file->TypeIs(file->DIRECTORY)) 	    { 	       if(recursion_mode==RECURSION_NEVER) 		  args.setarg(0,"rmdir"); 	       else 		  args.Append("-r"); 	    } 	    args.Append(target_session->GetFileURL(file->name)); 	    xstring_ca cmd(args.CombineQuoted()); 	    fprintf(script,"%s\n",cmd.get()); 	 } 	 if(!script_only) 	 { 	    ArgV *args=new ArgV("rm"); 	    args->Append(file->name); 	    args->seek(1); 	    rmJob *j=new rmJob(target_session->Clone(),args); 	    args->CombineTo(j->cmdline); 	    JobStarted(j); 	    if(file->TypeIs(file->DIRECTORY)) 	    { 	       if(recursion_mode==RECURSION_NEVER) 	       { 		  args->setarg(0,"rmdir"); 		  j->Rmdir(); 	       } 	       else 		  j->Recurse(); 	    } int   MirrorJob::Do() 	 if(!script_only) 	 { 	    ArgV *a=new ArgV("chmod"); 	    a->Append(file->name); 	    a->seek(1); 	    ChmodJob *cj=new ChmodJob(target_session->Clone(), 				 file->mode&~mode_mask,a); int   MirrorJob::Do() 	 if(!script_only) 	 { 	    ArgV *args=new ArgV("rm"); 	    args->Append(file->name); 	    args->seek(1); 	    rmJob *j=new rmJob(source_session->Clone(),args); 	    args->CombineTo(j->cmdline);

Fixed version (after fix):
 int   MirrorJob::Do() 	    } 	    continue; 	 } 	 bool use_rmdir = (file->TypeIs(file->DIRECTORY) 			   && recursion_mode==RECURSION_NEVER); 	 if(script) 	 { 	    ArgV args(use_rmdir?"rmdir":"rm"); 	    if(file->TypeIs(file->DIRECTORY) && !use_rmdir) 	       args.Append("-r"); 	    args.Append(target_session->GetFileURL(file->name)); 	    xstring_ca cmd(args.CombineQuoted()); 	    fprintf(script,"%s\n",cmd.get()); 	 } 	 if(!script_only) 	 { 	    ArgV *args=new ArgV(use_rmdir?"rmdir":"rm"); 	    args->Append(dir_file(".",file->name)); 	    args->seek(1); 	    rmJob *j=new rmJob(target_session->Clone(),args); 	    args->CombineTo(j->cmdline); 	    JobStarted(j); 	    if(file->TypeIs(file->DIRECTORY)) 	    { 	       if(recursion_mode==RECURSION_NEVER) 		  j->Rmdir(); 	       else 		  j->Recurse(); 	    } int   MirrorJob::Do() 	 if(!script_only) 	 { 	    ArgV *a=new ArgV("chmod"); 	    a->Append(dir_file(".",file->name)); 	    a->seek(1); 	    ChmodJob *cj=new ChmodJob(target_session->Clone(), 				 file->mode&~mode_mask,a); int   MirrorJob::Do() 	 if(!script_only) 	 { 	    ArgV *args=new ArgV("rm"); 	    args->Append(dir_file(".",file->name)); 	    args->seek(1); 	    rmJob *j=new rmJob(source_session->Clone(),args); 	    args->CombineTo(j->cmdline);

Patch:
patch: @@ -1164,35 +1164,29 @@ int   MirrorJob::Do()
 	    }
 	    continue;
 	 }
+	 bool use_rmdir = (file->TypeIs(file->DIRECTORY)
+			   && recursion_mode==RECURSION_NEVER);
 	 if(script)
 	 {
-	    ArgV args("rm");
-	    if(file->TypeIs(file->DIRECTORY))
-	    {
-	       if(recursion_mode==RECURSION_NEVER)
-		  args.setarg(0,"rmdir");
-	       else
-		  args.Append("-r");
-	    }
+	    ArgV args(use_rmdir?"rmdir":"rm");
+	    if(file->TypeIs(file->DIRECTORY) && !use_rmdir)
+	       args.Append("-r");
 	    args.Append(target_session->GetFileURL(file->name));
 	    xstring_ca cmd(args.CombineQuoted());
 	    fprintf(script,"%s\n",cmd.get());
 	 }
 	 if(!script_only)
 	 {
-	    ArgV *args=new ArgV("rm");
-	    args->Append(file->name);
+	    ArgV *args=new ArgV(use_rmdir?"rmdir":"rm");
+	    args->Append(dir_file(".",file->name));
 	    args->seek(1);
 	    rmJob *j=new rmJob(target_session->Clone(),args);
 	    args->CombineTo(j->cmdline);
 	    JobStarted(j);
 	    if(file->TypeIs(file->DIRECTORY))
 	    {
 	       if(recursion_mode==RECURSION_NEVER)
-	       {
-		  args->setarg(0,"rmdir");
 		  j->Rmdir();
-	       }
 	       else
 		  j->Recurse();
 	    }
@@ -1258,7 +1252,7 @@ int   MirrorJob::Do()
 	 if(!script_only)
 	 {
 	    ArgV *a=new ArgV("chmod");
-	    a->Append(file->name);
+	    a->Append(dir_file(".",file->name));
 	    a->seek(1);
 	    ChmodJob *cj=new ChmodJob(target_session->Clone(),
 				 file->mode&~mode_mask,a);
@@ -1380,7 +1374,7 @@ int   MirrorJob::Do()
 	 if(!script_only)
 	 {
 	    ArgV *args=new ArgV("rm");
-	    args->Append(file->name);
+	    args->Append(dir_file(".",file->name));
 	    args->seek(1);
 	    rmJob *j=new rmJob(source_session->Clone(),args);
 	    args->CombineTo(j->cmdline);
path: src/MirrorJob.cc