From 1551dbfde0e329783174b7aa9d1ce9fc93e8470b Mon Sep 17 00:00:00 2001 From: David Timber Date: Tue, 21 Nov 2023 19:55:39 +0800 Subject: Initial commit --- writeups/powershell-email/README.md | 43 ++++++++ writeups/powershell-email/sendmail.ps1 | 181 +++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 writeups/powershell-email/README.md create mode 100644 writeups/powershell-email/sendmail.ps1 (limited to 'writeups/powershell-email') diff --git a/writeups/powershell-email/README.md b/writeups/powershell-email/README.md new file mode 100644 index 0000000..4d815d4 --- /dev/null +++ b/writeups/powershell-email/README.md @@ -0,0 +1,43 @@ +# Sending Mail using Powershell +Turns out, Powershell can be used to send emails through harnessing the power of +C#. I made this script as a POC as to show how far .Net and Powershell have +come. + +Should work on all platforms that support Powershell. + +## Usage +Copied from the script. +```sh +echo 'Hi! it going? Testing my Powershell script.' | \ + smtp_host=smtp.gmail.com \ + smtp_username=example@gmail.com \ + smtp_password='0123456789' \ + mail_from=alice@gmail.com \ + mail_to=bob@example.com \ + mail_subject='Sent using Powershell' \ + sendmail.ps1 \ + doc.pdf +``` + +## Few Tips +### Password +Services like Gmail will require you to get a separate password for external +apps. Google calls this "App password". Refer to the links below. + +* https://support.google.com/accounts/answer/185833 +* https://support.google.com/mail/answer/7126229 + +Even if the normal password for the account can be used, a separate password +should always be used for program access. Always check if your email provider +supports this. + +### TLS +Most services will refuse to serve on unsecure connections. Use `smtp_tls=O` +only as the last resort. + +`smtp_tls_cert` is for TLS CN SASL authentication. if authenticating using this +method, `smtp_username` and `smtp_password` are not required. + +### CC and More +Didn't think about CC and all the advanced composition. Feel free to add more +feature to the script that is already monstrous! diff --git a/writeups/powershell-email/sendmail.ps1 b/writeups/powershell-email/sendmail.ps1 new file mode 100644 index 0000000..e5500ec --- /dev/null +++ b/writeups/powershell-email/sendmail.ps1 @@ -0,0 +1,181 @@ +#!/usr/bin/env pwsh + +# Send an email using Powershell. +# Usage: sendmail.ps1 [attachment 1 [attachment 2 [... attachment N]]] +# +# This is a POC on how to send an email using Powershell. The script should work +# on all platforms. The information required for the script to work is all +# supplied via environment variables. +# +# Env Vars +# smtp_host +# smtp_port (best if you let the implementation decide) +# smtp_tls: +# 'F' to insist on secure connection (default) +# 'O' for opportunistic +# 'N' to disable (default if $smtp_host is "localhost") +# smtp_tls_cert +# smtp_username +# smtp_password +# mail_from (required) +# mail_to (required) +# mail_subject (required) +# +# Example +#```pwsh +# echo 'Hi! it going? Testing my Powershell script.' | \ +# smtp_host=smtp.gmail.com \ +# smtp_username=example@gmail.com \ +# smtp_password='0123456789' \ +# mail_from=alice@gmail.com \ +# mail_to=bob@example.com \ +# mail_subject='Sent using Powershell' \ +# sendmail.ps1 \ +# doc.pdf +#``` +using namespace System +using namespace System.Net +using namespace System.Security.Cryptography + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" +$PSDefaultParameterValues['*:ErrorAction'] = 'Stop' + +<# +.SYNOPSIS +Get an environment variable and unset it + +.PARAMETER Name +The name of the environment variable to read and unset + +.PARAMETER Required +If set, throw FileNotFoundException if the environment variable requested is not +set + +.NOTES +The purpose of the function is to get and scrub off the password passed as an +env var in one go. To preserve the env var, use `GetEnvironmentVariable()` +directly. +#> +function FetchEnv ([string]$Name, [bool]$Required = $false) { + $RetVal = [Environment]::GetEnvironmentVariable($Name) + [Environment]::SetEnvironmentVariable($Name, '') + if ( $RetVal ) { + return $RetVal + } + else { + if ($Required) { + throw New-Object IO.FileNotFoundException ("${Name}: unset env var") + } + else { + return $null + } + } +} + +<# +.SYNOPSIS +Read data from STDIN until EOF and return data as decoded string +#> +function ExhaustStdin () { + $stream = New-Object IO.StreamReader ( [Console]::OpenStandardInput() ) + return $stream.ReadToEnd() +} + + +###################################################################### +# Execution starts here +###################################################################### + +# Compose a message +$mail = New-Object Mail.MailMessage ( + (FetchEnv "mail_from" $true), + (FetchEnv "mail_to" $true), + (FetchEnv "mail_subject" $true), + (ExhaustStdin)) # This is the part where the mail body is read from STDIN + +# Add attachments +foreach ($file in $args) { + [string]$file = $file + + $a = New-Object Mail.Attachment ( + $file, + # Treat all attachments as binary + [System.Net.Mime.MediaTypeNames+Application]::Octet) + # Timestamp support + $a.ContentDisposition.CreationDate = [IO.File]::GetCreationTime($file) + $a.ContentDisposition.ModificationDate = [IO.File]::GetLastWriteTime($file) + $a.ContentDisposition.ReadDate = [IO.File]::GetLastAccessTime($file) + + $mail.Attachments.Add($a) +} + +# Set up credentials +$client_cred = New-Object NetworkCredential ( + (FetchEnv "smtp_username"), + (FetchEnv "smtp_password")) + +# Set up client TLS cert +$tls_cert = FetchEnv("smtp_tls_cert") +if ($null -ne $tls_cert) { + $cert_chain = New-Object X509Certificates.X509Certificate ( $tls_cert ) +} +else { + $cert_chain = $null +} + +# Read target SMTP host +$smtp_host = FetchEnv "smtp_host" +if (!$smtp_host) { + $smtp_host = "localhost" +} + +# Set up SMTP client object +$smtp = New-Object Mail.SmtpClient ($smtp_host) +if ($cert_chain) { + $smtp.ClientCertificates.Add($cert_chain) +} +$smtp.Credentials = $client_cred +$smtp_port = FetchEnv "smtp_port" +if ($smtp_port) { + $smtp.Port = [int]$smtp_port +} + +$tlsmode = FetchEnv "smtp_tls" +if (!$tlsmode) { + # Determine the "tlsmode" to default to + if ($smtp_host -eq "localhost") { + # No need to waste computing power on TLS. + # Unless you're paranoid and don't trust the hosts file. + $tlsmode = "N" + } + else { + # Transmitting plain text data on the internet nowadays is no-brainer. + # Most email services will refuse anyway. + $tlsmode = "F" + } +} + +# Set `$smtp.EnableSsl` based on `$tlsmode` +if ($tlsmode -eq "F" -or $tlsmode -eq "O") { + $smtp.EnableSsl = $true +} +elseif ($tlsmode -eq "N") { + $smtp.EnableSsl = $false +} + +try { + $smtp.Send($mail) +} +catch { + if ($tlsmode -eq "O") { + # Opportunistic tlsmode. Try again with TLS disabled. + # Please think twice and fix the problem before resorting to this bit. + $smtp.EnableSsl = $false + $smtp.Send($mail) + } + else { + # Let the script die + throw $_ + } +} -- cgit