Excelのマクロやアドオンなどを利用する際に「これ使っても大丈夫なんだよね」と気になる事は無いでしょうか?
ではそうなると、「悪くない安全なマクロやアドオンとは?どのようなコーディンクになるのか?」という疑問が沸いてくるのですが、Microsoft ドキュメントの中を探してみても、これに答えてくれるページを見つける事はできませんでした。
そこでシリーズで「Excel VBAコーディングの安全性」について考えて行く事にしました。
シリーズ四回目の今回は、TCP/IPでのインターネット通信にはいろいろと規格(プロトコル)ありますが、その中でもhttp・https以外での規格(プロトコル)について取り上げています。
http|s以外の規格(プロトコル)で良く使われるものとしては、メールのSMTP・POP3・IMAPやファイル転送のFTP・FTPS・SFTP・SCPなどがありますが、実装の仕方や機能には多くのバリエーションが存在します。
ただし、まずは追加モジュールや環境設定は必要としない前提でこれらをすべて取り上げるとポイントが分かり難くなってしまうので、安全性を考える上で外せないメールであれば送信、ファイル転送であれば受信の方法を中心にご説明する事にします。
※動作は32bit版Excel 2016と64bit版Excel 2021の バージョン2309(ビルド 16827.20166)を使用して検証しています。
VBAでメールやFTPのインターネット通信をする時のバリエーション
メールやFTPについて一覧表にまとめて見ました。
すべてを網羅しているとは言い切れませんがWeb上で探すと見つかるものを列挙していますが、デフォルト環境のままで使えるやり方に限定しています。例えば.NET環境やbasp21などはインストールされていない前提です。
またコマンドラインシェル(バッチ”.bat”ファイルやPowerShellなど)を使ってインターネット通信をする事ができますが、これらにつきまして今回では無く別の回でまとめてご紹介したく存じます。
No | 名前 | ライブラリ/ 参照設定 | 種類 | 機能など |
---|---|---|---|---|
1 | InternetOpen InternetConnect FtpGetFile | wininet.dll | win32 | ftp通信 |
2 | CDO.Message | cdosys.dll/ Microsoft CDO for Windows 2000 Library | comクラス | コラボレーション データ オブジェクト (CDO) |
3 | Outlook.Application | MSOUTL.OLB/ Microsoft Outlook 16.0 Object Libray | comクラス | OutLookを使う |
4 | SendMail | - | ワークシート 関数 | OutLookを使う |
FTPは1つ、メールは3つほどやり方があるのですが、簡単に特徴をまとめます。
WinINet関数によるFTP通信
本シリーズは「Excel VBAコーディングの安全性」をテーマにしておりますが、win32APIの使用についてはすべてを掘り下げてご説明するのは難しいと感じています。
ただFTP通信に関してはコマンドラインシェルを除くとwin32API以外でのやり方が見つからなかったのでご紹介させていただきました。
なおコーディング事例で取り上げているのはFTPの機能の中で受信の機能だけにしぼっていますので、あらかじめお含み置きいただければ幸いです。
※コーディング事例を作成するに当たり次のサイトで主にwin32APIの呼び方を参考にさせていただきました。
また、本シリーズの補足記事としての「Excel VBAでWinINetの関数を使ってhttp|s通信を実装する」に掲載したHINTERNET型の階層の一覧表で、今回FTPの受信機能で使用するのは下図の赤枠で囲ったwin32APIになります。
コーディング事例
「Excel VBAでWinINetの関数を使ってhttp|s通信を実装する」でのコーティング事例に合わせたコーディングをしています。
InternetConnect関数は「Excel VBAでWinINetの関数を使ってhttp|s通信を実装する」でご説明していますが、FTPでは接続時にユーザーid・パスワードを設定するのでご注意ください。
- InternetConnect([1],[2],[3],[4],[5],[6],[7],[8])関数
- [4]:FTPのユーザーid
- [5]:[4]のパスワード
- FtpGetFile([1],[2],[3],[4],[5],[6],[7])関数 結果はブーリアン型
※ FTPサーバーからファイルを取得してローカルに指定したファイル名で保存します。- [1]:InternetConnect関数で取得したハンドル値をセットします。
- [2]:取得するファイル名
- [3]:ローカルでのファイル名
- [4]:True/Falseをセット。Trueをセットすると[3]が既存の場合は関数の結果はFalseになります。
- [6]:ファイル属性で、ASCII/バイナリ、読込の振る舞い方を設定
win32APIのDeclareステートメントを標準モジュールの先頭でコーディングします。
'WinINet関数の使用を初期化 Declare PtrSafe Function InternetOpen Lib "wininet.dll" Alias "InternetOpenA" ( _ ByVal lpszAgent As String, ByVal dwAccessType As Long, ByVal lpszProxyName As String, _ ByVal lpszProxyBypass As String, ByVal dwFlags As Long) As LongPtr '特定のサイトの HTTP セッションを開く Declare PtrSafe Function InternetConnect Lib "wininet.dll" Alias "InternetConnectA" ( _ ByVal hInternet As LongPtr, ByVal lpszServerName As String, ByVal nServerPort As Long, _ ByVal lpszUserName As String, ByVal lpszPassword As String, ByVal dwService As Long, _ ByVal dwFlags As Long, ByVal dwContext As LongPtr) As LongPtr 'FTP サーバーからファイルを取得し、指定したファイル名で格納 Declare PtrSafe Function FtpGetFile Lib "wininet.dll" Alias "FtpGetFileA" ( _ ByVal hConnect As LongPtr, ByVal lpszRemoteFile As String, ByVal lpszNewFile As String, _ ByVal fFailIfExists As Boolean, ByVal dwFlagsAndAttributes As Long, _ ByVal dwFlags As Long, ByVal dwContext As LongPtr) As Boolean '1 つのインターネットハンドルを閉じる Declare PtrSafe Function InternetCloseHandle Lib "wininet.dll" (ByVal hInternet As LongPtr) As Boolean
コンスタント変数 | 用途 |
---|---|
FTPSERVER | 接続先URL |
USER | FTPユーザid |
PASSWORD | 〃パスワード |
SERVERFOLDERPATH | 接続先のフォルダーパス |
TARGETFILE | 取得するファイル名 |
LOCALFOLDERPATH | 保存先のフォルダーパス |
FTPでデータを受信するためには下記の情報が必要になるのですが、今回はコンスタント変数にセットをしています。
Sub WininetFtpGet() Const FTPSERVER As String = "www....jp" '接続先URL Const USER As String = "....id" 'FTPユーザid Const PASSWORD As String = "....pw" '〃パスワード Const SERVERFOLDERPATH As String = "/..../public_html/" '接続先のフォルダーパス Const TARGETFILE As String = "ads.txt" '取得するファイル名 Const LOCALFOLDERPATH As String = "C:\Users\...\" '保存先のフォルダーパス ' Const INTERNET_OPEN_TYPE_PRECONFIG As Long = 0& Const INTERNET_SERVICE_FTP As Long = 1 Const FTP_TRANSFER_TYPE_UNKNOWN As Long = &H0 Const FTP_TRANSFER_TYPE_ASCII As Long = &H1 Const FTP_TRANSFER_TYPE_BINARY As Long = &H2 Const INTERNET_FLAG_RELOAD As Long = &H80000000 Dim hInternet As LongPtr, hConnect As LongPtr, hResult As LongPtr Dim l_ServerPort As Long Dim l_Flags As Long hInternet = InternetOpen("WininetOpenReqest", INTERNET_OPEN_TYPE_PRECONFIG, vbNullString, vbNullString, 0&) If hInternet = 0 Then Debug.Print Err.LastDllError & "-オープン:エラー." Exit Sub '<<<<<<<< End If l_ServerPort = 21& 'INTERNET_DEFAULT_FTP_PORT hConnect = InternetConnect(hInternet, FTPSERVER, l_ServerPort, USER, PASSWORD, INTERNET_SERVICE_FTP, 0&, 0&) If hConnect = 0 Then Debug.Print Err.LastDllError & "-コネクト:エラー." Call InternetCloseHandle(hInternet) Exit Sub '<<<<<<<< End If 'ファイルを受信 l_Flags = FTP_TRANSFER_TYPE_ASCII Or INTERNET_FLAG_RELOAD hResult = FtpGetFile(hConnect, SERVERFOLDERPATH & TARGETFILE, LOCALFOLDERPATH & TARGETFILE, False, 0&, l_Flags, 0&) If (hResult) Then Debug.Print "受信OK" Else Debug.Print Err.LastDllError & "-受信:エラー." End If Call InternetCloseHandle(hConnect) Call InternetCloseHandle(hInternet) End Sub
Windows ファイアウォールが「Windows セキュリティの重要な警告」ポップアップを表示する条件
このコーディングを実行する際にWindowsファイアウォールによって「Windows セキュリティの重要な警告」のポップアップを表示する場合があります。
これについては下記の「日本マイクロソフトWindows Supportチームによる、サポート情報Blog」に記載されていますので、ご参照いただければ幸いです。
ただし、「この機能があるからwin32APIでFTPを実行する悪意のあるコーディングは作れない」とするのはリスクがあるまで、きちっと「悪意のあるプログラム」になっていない事を確認する必要がある認識です。
FTPではどこを確認すべきか?
FTPの場合もhttp|s通信の時と同様に接続先URLを確認する必要があります。
受信するデータがASCIIかBINARYかはFtpGetFile関数で指定をしていますが、ASCIIが指定されていてたとしても「それなら大丈夫」とはならない事は、「Excel VBAコーディングの安全性とは「(2)EXEやソースの隠蔽」でワークシートに16進表記でバイナリーファイルを格納できる事からご理解いただけるものと存じます。
なお隠し先は単にワークシートだけではなく「Excelファイル内のいろいろな場所」に隠す事ができるのですが、FTPを使えばその隠し場所が「インターネット通信できるどこか」になる分けで、より多くの悪意のあるデータ、しかもアップデートされた情報を取得できることになりますので注意が必要です。
コラボレーション データ オブジェクト (CDO)を使ったメール送信
Windows 2000 ライブラリ (Cdosys.dll) のコラボレーション データ オブジェクト (CDO) 1.2.1を使用して電子メール メッセージを送信する事ができます。
※CDOはExtend MAPI機能をラップして提供するためのクライアントライブラリになります。
ただし下記のMicrosoft ドキュメントに書かれているように、Outlook2010以降のバージョンがインストールされている場合は推奨もサポートもされないようです。そもそもOutlookオブジェクトモデルが大幅に拡張されたOutlook 2007以前から存在する歴史のあるクライアントライブラリです。
※ただし入っていない時はCDO1.2.1を使用する事に問題は無いと書かれています。
また次のMicrosoftドキュメントは英語版しか用意されていませんがCDOfor Exchange 2000 Server (CDOEX)の「configuration/名前空間」に関すドキュメントが公開されています。
ただしドキュメントの書出しにはすべてつぎのような「断り書き」が付けられています。
このコンテンツは現在、積極的にメンテナンスされていません。これらのテクノロジを現在も使用しているすべての人に対して現状のまま提供され、最新の製品バージョンまたはサービス リリースに関する正確性の保証または主張はありません。
Google Translate
だからと言って「悪意のあるプログラム」で使用されないとは言う事はできません。
なおコーディング事例を作成するにあたり下記のサイト「blog808」を参考にさせていただきました。
コーディング事例
メールを送信するためには、①送信メールの情報と②送信先のメールサーバーの情報が必要です。
参考にしたサイト「blog808」ではYahooメールとGmailの場合のコーディング事例を掲載していますので、それ以外でSMTP認証をしている場合のコーディング事例をご紹介いたします。
メールを送信するためには①送信するメール自身の情報と②送信先のメールサーバの情報が必要になるのですが、今回はコンスタント変数にセットをしています。
①送信メールの情報
コンスタント変数 | 用途 |
---|---|
MAILFROM | 送信者 |
MAILTO | 宛先 |
SUBJECT | 件名 |
BODY | 本文 |
②送信先メールサーバーの情報
名前空間 | コンスタント変数 | 用途 |
---|---|---|
smtpserver | SMTPSERVER | 送信先SMTPサーバのURL |
sendusing | SENDUSING | 値:1~3 何のSMTPを使うか |
smtpserverport | SERVERPORT | 使用するSMTPポートの番号 |
smtpconnectiontimeout | TIMEOUT | 接続試行が中止されるまでの秒数 |
smtpauthenticate | CDOBASIC | 値:0~2 認証メカニズム |
smtpusessl | - | SSLを使用するかのBOOL値 |
sendusername | SENDUSERNAME | SMTP認証ユーザーID |
sendpassword | SENDPASSWORD | SMTP認証パスワード |
languagecode | - | 文字コードを指定する |
Private Sub SendMailByCDO() Const SMTPSERVER As String = "smtp.....jp" Const SENDUSING = 2 '1はローカルにSMTPサーバーが存在する時 Const SERVERPORT As Integer = 465 '接続先SMTPサーバーの設定に従う Const TIMEOUT As Integer = 30 Const CDOBASIC = 1 '基本 (クリア テキスト) 認証メカニズムを使用 Const SENDUSERNAME As String = ".....@from.....jp" Const SENDPASSWORD As String = "....." Const NS As String = "http://schemas.microsoft.com/cdo/configuration/" Const MAILFROM As String = ".....@from.....jp" Const MAILTO As String = "......@to.....jp" Const SUBJECT As String = "VBA Send Mail By CDO" Const BODY As String = "Hollow World!" Dim objCDO As Object Set objCDO = CreateObject("CDO.Message") 'New CDO.Message With objCDO With .Configuration.Fields .Item(NS & "smtpserver") = SMTPSERVER ' SMTPサーバURL .Item(NS & "sendusing") = SENDUSING ' SMTPを指定 .Item(NS & "smtpserverport") = SERVERPORT ' SMTPポート .Item(NS & "smtpconnectiontimeout") = TIMEOUT ' 接続試行のタイムアウト .Item(NS & "smtpauthenticate") = CDOBASIC ' 認証メカニズムの指定 .Item(NS & "smtpusessl") = True ' SSLを指定(接続先SMTPサーバーの設定に従う) .Item(NS & "sendusername") = SENDUSERNAME ' SMTP認証ユーザーID .Item(NS & "sendpassword") = SENDPASSWORD ' SMTP認証パスワード .Item(NS & "languagecode") = "shift-jis" ' 文字セット指定 .Update ' 設定を更新 End With .MimeFormatted = True 'MIMEを使って書式設定 .From = MAILFROM ' 送信者 .To = MAILTO ' 宛先 .SUBJECT = SUBJECT ' 件名 .TextBody = BODY ' 本文 .send ' 送信 End With Set objCDO = Nothing End Sub
Outlookを使ったメール送信
OutlookはいままではOffice製品が端末にインストールされている必要があったのですが、2024年にWindowsメールがOutlookに切り替えられるために標準で使えるようになります。
※ただしメール送信するためには何かしらメールアカウントがOutlookに登録されてSMTP接続先が設定されていなければなれません。
そいうぃ意味ではOutlookをまったく使用していなければこのやり方は成立しないのですが、今後はある程度の方々が使われると思いますのでご紹介いたします。
Outlook.Applicationを使用する方法
このやり方については下記のMicrosoftドキュメントにVBAでのコーディング事例が掲載されていますが、下記のような注釈が書かれています。
※CDOの時とは少し内容が異なっています。
Microsoft は、例示のみを目的としてプログラミング例を提供しており、明示または黙示にかかわらず、いかなる責任も負わないものとします。
いまいま、たくさんのスパムメールが飛び交っている状況なのですが「悪意のあるプログラム」について触れない所に違和感を感じますが、それに触れだすといろいろなところに注釈が必要になり「収拾がつかなくなる」と考えてるのかもしれません…
このサインプルではワークシート上のデータを読み込んでメールを送信する形になっているので、簡潔にしたコーディング事例をご紹介いたします。
コーディング事例
前回と同じように、「送信するメール自身の情報」はコンスタント変数にセットをしています。
※なお「送信先メールサーバーの情報」はOutlookに設定されている情報を使用するためいりません。
コンスタント変数 | 用途 |
---|---|
MAILTO | 宛先 |
SUBJECT | 件名 |
BODY | 本文 |
※MAILFROMの情報はOutlookに設定されている情報が読み込まれてセットされるので必要ありません。
OutlookのApplicationオブジェクトのCreateItemメソッドに指定できるパラメーターについては、次のMicrosoftドキュメントをご参照ください。
※今回はMailItemオブジェクトを使用します。
また本文の形式を設定するMailItemオブジェクトのBodyFormatプロパティについては「OlBodyFormat列挙」をご参照ください。
Sub outlookAppSend() Const MAILTO As String = "...@to....jp" ' 宛先 Const SUBJECT As String = "VBA Send Mail Test" '件名 Const BODY As String = "Hollow World!" '本文 Const olMailItem As Integer = 0 'MailItem オブジェクト Const olFormatPlain As Integer = 1 '本文の形式:テキスト形式 Dim objOutlook As Object Dim objMail As Object Dim i As Integer Set objOutlook = CreateObject("Outlook.Application") Set objMail = objOutlook.CreateItem(olMailItem) With objMail .To = MAILTO .SUBJECT = SUBJECT .bodyformat = olFormatPlain .BODY = BODY .send End With Set objMail = Nothing Set objOutlook = Nothing End Sub
WorkbookオブジェクトのSendMailメソッドを使用する方法
このやり方はコーディング量が一番少なく、VBAを実行している自分自身を添付ファイルにして宛先に送信します。
ただし次のような制約がかかっていて簡単にメールを送信できるわけではありませんので、あらかじめお含み置きください。
送信に必要な前提条件は次のようになります。
- メール送信するためには何かしらメールアカウントがOutlookに登録されてSMTP接続先が設定されていなければなれません。
- 実行するとOutlookが次のようなポップアップを表示するので「許可」をクリックしないと送信できません。
- 「許可」するとOutlookの送信トレイに保存されます。この状態でOutlookを起動すると(Outlookの設定にもよるかもしれませんが)送信されます。
※Outlookが起動している場合は送信トレイに保存されたままになります。
コーディング事例
今回コンスタント変数にセットが必要なのは下記の2つだけになます。
コンスタント変数 | 用途 |
---|---|
MAILTO | 宛先 |
SUBJECT | 件名 |
ActiveWorkbookオブジェクトのSendMailメソッドに指定できるパラメーターについては、次のMicrosoftドキュメントをご参照ください。
またAppicationオブジェクトのMailSystemプロパティのリンクはこちらです。
https://learn.microsoft.com/ja-jp/office/vba/api/excel.application.mailsystem
Sub WorkbookSendMail() Const MAILTO As String = ".....@to.....jp" ' 宛先 Const SUBJECT As String = "VBA Send Mail By Workbook SendMail" If IsNull(Application.MailSession) Then Application.MailLogon If Application.MailSystem = xlMAPI Then ActiveWorkbook.SendMail Recipients:=MAILTO, SUBJECT:=SUBJECT Else MsgBox "Can not send" End If Application.MailLogoff End Sub
メール送信ではどこを確認すべきか?
コーディングを解析して、「何を送ろうとしているのか?」を知るべきだとは思いますが、まずは「誰にどのくらいのタイミングで送ろうとしているのか?」を最初に確認するべきだと思います。
誰は身内なら良いという訳ではなく、タイミングも1日1回だから安心という訳ではありません。
ただかなりな人数に頻繁に送信をしているのであれば「悪意がある」と判断できると思います。
まずはこれに調査対象を絞ったうえで、判断に迷うところがあれば「何を送ろうとしているか?」調べるのが必要な時間を短縮できるので良いと思います。
なおファイルを添付して送ろうとしている場合であれば、最初に「 何を送ろうとしているか?」確認した方が良いかもしれません。
できる事ならFTP通信やメール送信はしないに越したことは無い
前回のHttp|s通信でも同じような事を書きましたが、無理にFTPで受信をしたりメール送信をしなくて済むように代替手段を設定する方が「悪意あるプログラム」というあらぬ疑いをかけられずに済むのではないでしょうか?
ただ現実としてFTPじゃないとデータを受け取れないとか、メールで知らせて欲しいとかの要望を受ける事があるのかもしれません。
FTPはプロトコルの制約なので他の手段に変える事は難しいですがメールは「プッシュ型の通知」と考えると、ユーザ情報を持ったメッセージをテーブルに書き込んでおいて、それを仕事で使用するログインが必要なWebページのどこかで、メッセージとして「情報を送り付ける事」ができるように仕組みを作っておけば良いのだと思います。
ただし、そのようなWebページが存在しない場合はメールが手っ取り早い事にはなりますが…
今回のまとめと次回の予告
今回はTCP/IPでのインターネット通信の中でhttp・https以外の規格(プロトコル)について取り上げてきました。
http・httpsのように多くのバリエーションがある分けではありませんが、悪意をもって使用されるとリスクになる認識です。
なおDeclareステートメントでWin32 APIを使用するケースに関しては、本シリーズではあまり深く立ち入らないつもりですが、FTPに関してはやむを得ず説明いたしました。
コミュニケーション手段としてのメールについては将来も残るとは思いますが、別なプッシュ型通達手段も使われるようになるのでは?と思います。
次回はVBAからコマンドラインシェルやスクリプトファイルの実行について取り上げる予定です。
以上最後までご一読いただき誠にありがとうございました。