Transferts FTPS en PowerShell

Bonjour à tous, aujourd’hui je vous repartage quelques morceaux de PowerShell dont j’ai dû me servir récemment pour faire des transferts FTP et FTPS en PowerShell au taf. C’est un peu plus propre que mon hack de transfert reverse HTTP de la dernière fois… Ça peut vous servir pour automatiser des transferts, ou sauvegarde, faire de l’exfiltration en offensif ou collecter des données depuis des machines en live-forensics par exemple. Aller sans transition je vous passe les différents bout de code que j’ai eu à faire.

Upload de fichier en FTP

Le premier bout de code est tiré de ce Stack Overflow et permet d’uploader facilement un fichier vers un serveur FTP :

$File = "Fichier.zip";
$ftp = "ftp://etienene:Passw0rd1234!@ftp.geekeries.org/testps1/somefilename.zip";
$webclient = New-Object -TypeName System.Net.WebClient;
$uri = New-Object -TypeName System.Uri -ArgumentList $ftp;
$webclient.UploadFile($uri, $File);

C’est tout con, ça tient en 5 lignes, et on peut faire l’exfiltration ou du backup facilement avec ça. Je vous renvoie vers la création d’un serveur FTP avec docker pour monter un serveur qui reçoit les données. N’oubliez pas l’existence de la fonction DownloadFile qui existe dans le même objet WebClient pour télécharger vos fichiers ensuite.

Transferts FTPS en PowerShell : upload

Bon c’est joli au-dessus mais ça ne supporte pas le protocole FTPS (à ne pas confondre avec le SFTP, hein) du coup ont doit changer d’objet .NET pour supporter le FTPS et utiliser System.Net.FtpWebRequest qui lui supporte bien le chiffrement par certificats de la communication. Plus de fonction Upload et DowloadFile direct on est obligé de se taper le transfert octet par octet. La fonction d’envoi de fichier (que j’ai piqué ici) devient :

Function Send-FTPSFile {
	param (
		[string]$file,
		[string]$ftphostpath,
		[string]$username,
		[string]$password
	)

	
	$f = Get-Item $file
	$req = [System.Net.FtpWebRequest]::Create("ftp://$ftphostpath/" + $f.Name);
	$req.Credentials = New-Object System.Net.NetworkCredential($username, $password);
	$req.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile;
	$req.EnableSsl = $True;
	$req.UseBinary = $True
	$req.UsePassive = $True
	$req.KeepAlive = $True
	$req.ConnectionGroupName = "FTPS$username";

	$fs = new-object IO.FileStream $f.FullName, 'Open', 'Read'
	$ftpStream = $req.GetRequestStream();
	$req.ContentLength = $f.Length;

	try {
		$b = new-object Byte[](10000)

		while($true) {
			$r = $fs.Read($b, 0, 10000);
			if($r -eq 0) { break; }
			$ftpStream.Write($b, 0, $r);
		}

	} finally {
		if ($fs -ne $null) { $fs.Dispose(); }
		$ftpStream.Close();    
		$resp = $req.GetResponse();
		$resp.StatusDescription;
		$resp.Close();
	}
}

A notez qu’on trouve pas mal d’alternatives (ici ou ) sur le net dans la manière de faire, mais ça reviens toujours un peu à ça au final quand on regarde bien.

Création d’un dossier en FTPS

Du coup, on peut continuer en gérant gérer nos dossiers sur le FTP avec la fonction ci-dessous (trouvée ):

function New-FtpDirectory {
param(
[Parameter(Mandatory=$true)]
[string]
$sourceuri,
[Parameter(Mandatory=$true)]
[string]
$username,
[Parameter(Mandatory=$true)]
[string]
$password
)
if ($sourceUri -match '\\$|\\\w+$') { throw 'sourceuri should end with a file name' }
$ftprequest = [System.Net.FtpWebRequest]::Create($sourceuri);
$ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::MakeDirectory
$ftprequest.EnableSsl = $True;
$ftprequest.UseBinary = $True
$ftprequest.UsePassive = $True
$ftprequest.KeepAlive = $True

$ftprequest.Credentials = New-Object System.Net.NetworkCredential($username,$password)

$response = $ftprequest.GetResponse();

Write-Host Upload File Complete, status $response.StatusDescription

$response.Close();
}

Comme d’habitude ça fonctionne bien et avec une gestion d’erreur minimaliste on se retrouve rapidement à automatiser des uploads dans une arborescence de dossier sur notre serveurs FTP.

Ignorer les certificats autosignés

Si vous avez besoin de passer par des certificats autosignés pour votre FTPS, un hack rapide (mais pas trop secure) pour ignorer la Validation du certificat :

if (-not ([System.Management.Automation.PSTypeName]'CertValidation').Type) {
	Add-Type @"
		using System.Net;
		using System.Net.Security;
		using System.Security.Cryptography.X509Certificates;
		public class CertValidation
		{
			static bool IgnoreValidation(object o, X509Certificate c, X509Chain ch, SslPolicyErrors e) {
				return true;
			}
			public static void Ignore() {
				ServicePointManager.ServerCertificateValidationCallback = IgnoreValidation;
			}
			public static void Restore() {
				ServicePointManager.ServerCertificateValidationCallback = null;
			}
		}
"@
}

[CertValidation]::Ignore()
# FTP actions here
[CertValidation]::Restore()

Un bout de script transferts FTPS en PowerShell

$username = "etienne"
$password = "1234lol"
$rootdirname = "GEEKERIES_ORG"
$hostname = $env:COMPUTERNAME
$client = New-Object System.Net.WebClient

#$client.Credentials = New-Object System.Net.NetworkCredential("$username", "$password")
$filename = "test.txt"

[CertValidation]::Ignore()

#$ErrorActionPreference = 'SilentlyContinue'
New-FtpDirectory -sourceuri "ftps://ftp.geekeries.org:21/backups/$rootdirname/" -username $username -password $password
New-FtpDirectory -sourceuri "ftps://ftp.geekeries.org:21/backups/$rootdirname/$hostname" -username $username -password $password
#$ErrorActionPreference = 'Continue'

Send-FTPSFile -file "$filename" -ftphostpath "ftp.geekeries.org:21/backups/$rootdirname/$hostname" -username $username -password $password

Et voilà, c’est tout pour aujourd’hui sur les transferts FTPs en PowerShell, pour ceux qui ont déjà testé sous linux en bash (en Python toutes les libs qui vont bien existent), moi je trouve ça beaucoup plus simple et clair en PowerShell ;-). Amusez-vous bien avec ça et à la prochaine !

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.