输入和输出
输入控制
我们可能会从我们发送的请求和执行的命令中得到结果,我们必须手动决定如何继续。另一个例子是我们在为不同场景设计的脚本中定义了几个函数。我们必须在手动检查后并根据结果来决定应该执行哪些。也很有可能不允许执行特定的扫描或活动。因此,我们需要熟悉如何获得一个运行脚本来等待我们的指令。如果我们CIDR.sh
再次查看我们的脚本,我们会看到我们添加了这样一个调用来决定进一步的步骤。
CIDR.sh
# Available options
<SNIP>
echo -e "Additional options available:"
echo -e "\t1) Identify the corresponding network range of target domain."
echo -e "\t2) Ping discovered hosts."
echo -e "\t3) All checks."
echo -e "\t*) Exit.\n"
read -p "Select your option: " opt
case $opt in
"1") network_range ;;
"2") ping_host ;;
"3") network_range && ping_host ;;
"*") exit 0 ;;
esac
第一echo
行用作我们可用选项的显示菜单。使用该read
命令,将Select your option:
显示带有“”的行,并且附加选项-p
确保我们的输入保持在同一行。我们的输入存储在变量opt
中,然后我们用它来执行带有case
语句的相应函数,我们稍后会看到。根据我们输入的数字,case
语句确定执行哪些函数。
输出控制
我们已经了解了Linux Fundamentals
模块中输出的输出重定向。然而,重定向的问题是我们没有从相应的命令中获得任何输出。它将被重定向到适当的文件。如果我们的脚本以后变得更复杂,它们可能需要更多的时间,而不仅仅是几秒钟。为了避免无所事事地等待脚本的结果,我们可以使用tee实用程序。它确保我们立即看到我们得到的结果,并将它们存储在相应的文件中。在我们的CIDR.sh
脚本中,我们以不同的方式两次使用了这个实用程序。
CIDR.sh
<SNIP>
# Identify Network range for the specified IP address(es)
function network_range {
for ip in $ipaddr
do
netrange=$(whois $ip | grep "NetRange\|CIDR" | tee -a CIDR.txt)
cidr=$(whois $ip | grep "CIDR" | awk '{print $2}')
cidr_ips=$(prips $cidr)
echo -e "\nNetRange for $ip:"
echo -e "$netrange"
done
}
<SNIP>
# Identify IP address of the specified domain
hosts=$(host $domain | grep "has address" | cut -d" " -f4 | tee discovered_hosts.txt)
<SNIP>
使用时tee
,我们将接收到的输出传输并使用管道(|
)将其转发到tee
. “ -a
/ --append
”参数确保指定的文件不被覆盖,而是用新的结果进行补充。同时,它向我们展示了结果以及如何在文件中找到它们。
darkinga@htb[/htb]$ cat discovered_hosts.txt CIDR.txt
165.22.119.202
NetRange: 165.22.0.0 - 165.22.255.255
CIDR: 165.22.0.0/16
Flow Control - 循环(Loops)
控制我们的脚本流程是必不可少的。我们已经了解了if-else
条件,这也是流量控制的一部分。毕竟,我们希望我们的脚本能够快速高效地工作,为此,我们可以使用其他组件来提高效率并允许无错误处理。每个控制结构都是 abranch
或 a loop
。布尔值的逻辑表达式通常控制控制结构的执行。这些控制结构包括:
-
分支机构:
If-Else
条件Case
声明-
循环:
-
For
循环 While
循环Until
循环
For 循环
让我们从For
循环开始。循环在For
每次传递时执行一个参数,shell 从列表中获取,从增量计算,或者从另一个数据源获取。只要找到相应的数据,for 循环就会运行。这种类型的循环可以以不同的方式构建和定义。例如,当我们需要处理数组中的许多不同值时,通常会使用 for 循环。这可用于扫描不同的主机或端口。我们还可以使用它对已知端口及其服务执行特定命令,以加快我们的枚举过程。其语法如下:
语法 - 示例
for $variable in 1 2 3 4
do
echo $variable
done
for $variable in file1 file2 file3
do
echo $variable
done
for ip in "10.10.10.170 10.10.10.174 10.10.10.175"
do
ping -c 1 $ip
done
当然,我们也可以将这些命令写在一行中。这样的命令看起来像这样:
$ for ip in 10.10.10.170 10.10.10.174;do ping -c 1 $ip;done
PING 10.10.10.170 (10.10.10.170): 56 data bytes
64 bytes from 10.10.10.170: icmp_seq=0 ttl=63 time=42.106 ms
--- 10.10.10.170 ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 42.106/42.106/42.106/0.000 ms
PING 10.10.10.174 (10.10.10.174): 56 data bytes
64 bytes from 10.10.10.174: icmp_seq=0 ttl=63 time=45.700 ms
--- 10.10.10.174 ping statistics ---
1 packets transmitted, 1 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 45.700/45.700/45.700/0.000 ms
让我们再看看我们的CIDR.sh
脚本。我们在脚本中添加了几个 for 循环,但让我们坚持这个小代码部分。
CIDR.sh
<SNIP>
# Identify Network range for the specified IP address(es)
function network_range {
for ip in $ipaddr
do
netrange=$(whois $ip | grep "NetRange\|CIDR" | tee -a CIDR.txt)
cidr=$(whois $ip | grep "CIDR" | awk '{print $2}')
cidr_ips=$(prips $cidr)
echo -e "\nNetRange for $ip:"
echo -e "$netrange"
done
}
<SNIP>
与前面的示例一样,对于数组 " ipaddr
" 中的每个 IP 地址,我们发出一个 " whois
" 请求,其输出被过滤为 " NetRange
" 和 " CIDR
。" 这有助于我们确定目标所在的地址范围。我们可以使用这些信息在渗透测试期间搜索其他主机,if approved by the client
. 我们收到的结果会相应显示并存储在文件“ CIDR.txt
.”中。
While 循环
循环在while
概念上很简单,遵循以下原则:
- 只要满足条件(
true
),就会执行语句。
我们还可以组合循环并将它们的执行与不同的值合并。需要注意的是,多个循环相互过度组合会使代码非常不清楚,并导致难以查找和跟踪的错误。这样的组合可以看起来像我们的CIDR.sh
脚本。
CIDR.sh
<SNIP>
stat=1
while [ $stat -eq 1 ]
do
ping -c 2 $host > /dev/null 2>&1
if [ $? -eq 0 ]
then
echo "$host is up."
((stat--))
((hosts_up++))
((hosts_total++))
else
echo "$host is down."
((stat--))
((hosts_total++))
fi
done
<SNIP>
while
循环也适用于if-else
. 当一个while循环必须停止执行它包含的命令时,它需要某种计数器来定位自己。否则,这会导致无限循环。这样的计数器可以是我们用特定值或布尔值声明的变量。While
当布尔值为“ True
”时循环运行。除了计数器之外,我们还可以使用命令“ ” break
,它会在到达该命令时中断循环,如下例所示:
WhileBreaker.sh
#!/bin/bash
counter=0
while [ $counter -lt 10 ]
do
# Increase $counter by 1
((counter++))
echo "Counter: $counter"
if [ $counter == 2 ]
then
continue
elif [ $counter == 4 ]
then
break
fi
done
WhileBreaker.sh
darkinga@htb[/htb]$ ./WhileBreaker.sh
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Until Loops
还有until
循环,比较少见。尽管如此,until
循环的工作方式与循环完全相同while
,但不同之处在于:
until
只要特定条件为 ,就会执行循环内的代码false
。
另一种方法是让循环运行直到达到所需的值。" until
" 循环非常适合这种情况。这种循环的工作方式与 " while
" 循环类似,但如前所述,不同之处在于它一直运行到布尔值为 " False
。"
Until.sh
#!/bin/bash
counter=0
until [ $counter -eq 10 ]
do
# Increase $counter by 1
((counter++))
echo "Counter: $counter"
done
Until.sh
$ ./Until.sh
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
Counter: 6
Counter: 7
Counter: 8
Counter: 9
Counter: 10
练习脚本
#!/bin/bash
# Decrypt function
function decrypt {
MzSaas7k=$(echo $hash | sed 's/988sn1/83unasa/g')
Mzns7293sk=$(echo $MzSaas7k | sed 's/4d298d/9999/g')
MzSaas7k=$(echo $Mzns7293sk | sed 's/3i8dqos82/873h4d/g')
Mzns7293sk=$(echo $MzSaas7k | sed 's/4n9Ls/20X/g')
MzSaas7k=$(echo $Mzns7293sk | sed 's/912oijs01/i7gg/g')
Mzns7293sk=$(echo $MzSaas7k | sed 's/k32jx0aa/n391s/g')
MzSaas7k=$(echo $Mzns7293sk | sed 's/nI72n/YzF1/g')
Mzns7293sk=$(echo $MzSaas7k | sed 's/82ns71n/2d49/g')
MzSaas7k=$(echo $Mzns7293sk | sed 's/JGcms1a/zIm12/g')
Mzns7293sk=$(echo $MzSaas7k | sed 's/MS9/4SIs/g')
MzSaas7k=$(echo $Mzns7293sk | sed 's/Ymxj00Ims/Uso18/g')
Mzns7293sk=$(echo $MzSaas7k | sed 's/sSi8Lm/Mit/g')
MzSaas7k=$(echo $Mzns7293sk | sed 's/9su2n/43n92ka/g')
Mzns7293sk=$(echo $MzSaas7k | sed 's/ggf3iunds/dn3i8/g')
MzSaas7k=$(echo $Mzns7293sk | sed 's/uBz/TT0K/g')
flag=$(echo $MzSaas7k | base64 -d | openssl enc -aes-128-cbc -a -d -salt -pass pass:$salt)
}
# Variables
var="9M"
salt=""
hash="VTJGc2RHVmtYMTl2ZnYyNTdUeERVRnBtQWVGNmFWWVUySG1wTXNmRi9rQT0K"
# Base64 Encoding Example:
# $ echo "Some Text" | base64
# <- For-Loop here
# Check if $salt is empty
if [[ ! -z "$salt" ]]
then
decrypt
echo $flag
else
exit 1
fi
创建一个“for”循环,在“base64”中对变量“var”进行 28 次编码。第 28 个哈希中的字符数是必须分配给“salt”变量的值。
Flow Control - Branches(分支)
正如我们已经看到的,流控制中的分支包括if-else
和case
语句。我们已经详细讨论了这些if-else
语句,并且知道它是如何工作的。现在我们将仔细研究案例陈述。
案例陈述
Case
语句在其他语言中也称为switch-case
语句,例如 C/C++ 和 C#。if-else
和switch-case
之间的主要区别在于if-else
构造允许我们检查任何布尔表达式,而switch-case
始终只将变量与确切值进行比较。因此,不允许使用与 if-else
相同的条件,例如“大于” 。switch-case
语句的语法如下所示:
语法 - Switch-Case
case <expression> in
pattern_1 ) statements ;;
pattern_2 ) statements ;;
pattern_3 ) statements ;;
esac
switch-case 的定义以 case
开头,后跟作为表达式的变量或值,然后在模式中进行比较。如果变量或值与表达式匹配,则语句在括号后执行并以双分号 ( ;;
) 结束。
在我们的CIDR.sh
脚本中,我们使用了这样的case
语句。在这里,我们定义了分配给脚本的四个不同选项,在我们做出决定后它应该如何进行。
CIDR.sh
<SNIP>
# Available options
echo -e "Additional options available:"
echo -e "\t1) Identify the corresponding network range of target domain."
echo -e "\t2) Ping discovered hosts."
echo -e "\t3) All checks."
echo -e "\t*) Exit.\n"
read -p "Select your option: " opt
case $opt in
"1") network_range ;;
"2") ping_host ;;
"3") network_range && ping_host ;;
"*") exit 0 ;;
esac
<SNIP>
使用前两个选项,此脚本执行我们之前定义的不同功能。使用第三个选项,两个函数都被执行,而使用任何其他选项,脚本将被终止。