From WikiChip
Difference between revisions of "mirc/msl injection"
< mirc

Line 25: Line 25:
 
Now, the language features some commands where one or more of their parameters are actually meant to be a new command with its own parameters. That new command is meant to be used later automatically by mIRC, this is where the injection happens.
 
Now, the language features some commands where one or more of their parameters are actually meant to be a new command with its own parameters. That new command is meant to be used later automatically by mIRC, this is where the injection happens.
  
Any command like this suffers the problem, they are fortunately not a lot, here is a list:
+
Any command like this suffers the problem, we also refer to this problem as the double evaluation problem. They are fortunately not a lot of these commands, here is a list:
  
 
* /timer
 
* /timer
Line 31: Line 31:
 
* /scid
 
* /scid
  
//but the 3 most common commands that possess the ability to dynamically evaluate code are the /timer, /scon, and /scid commands. The $eval() (and its aliases $() and $evalnext()) can also dynamically //evaluate code.
+
=== The /timer command ===
 +
Perhaps the most problematic command of them all is the /timer command. The /timer command is used to delay the execution of another command (it has others features but this is the interesting one).
 +
 
 +
Because of this, you must pass a new command to be executed after the delay to the timer command:
 +
 
 +
<syntaxhighlight lang="mirc">
 +
//.timer 1 1 echo -a $day
 +
</syntaxhighlight>
 +
Which will execute "echo -a $day" one time, after waiting 1 second.
 +
 
 +
What happens here is that the parameters passed to the /timer command are evaluated once as we saw, so $day gets evaluated once, producing the current day.
  
=== The /timer command ===
+
The timer will register that its associated command to be executed when the delay has passed is "echo -a <$day's value>"
Perhaps the most problematic command of them all is the /timer command. In addition to the way things are evaluated normally, everything passed to the timer is evaluated a second time when the timer is triggered.  
+
 
 +
When the timer fires, its command (a new command) is executed and therefore all of its parameters are, once again, evaluated one time. In this example there is no problem and we can't see the difference because $day can only return a day of the week. If $day is monday, evaluating the plain text monday will always produce monday.
  
Let's take a look at an innocent reminder script:
+
But what if we didn't have $day? what if we had something like $2? Let's consider a more useful example, an innocent reminder script:
  
 
<syntaxhighlight lang="mirc">on *:text:!reminder & *:#:{
 
<syntaxhighlight lang="mirc">on *:text:!reminder & *:#:{
   timer 1 $2 notice $nick [Reminder] $3- (Set $2 seconds ago)
+
if ($2 !isnum) {
 +
    notice $nick [Reminder] Syntax Error: !reminder <seconds> <message>
 +
    return
 +
   }
 +
  .timer 1 $2 notice $nick [Reminder] $3- (Set $2 seconds ago)
 
}</syntaxhighlight>
 
}</syntaxhighlight>
  
Line 47: Line 62:
 
-Bot- [Reminder] jill's birthday tomorrow! (Set 18000 seconds ago)</pre>
 
-Bot- [Reminder] jill's birthday tomorrow! (Set 18000 seconds ago)</pre>
  
Although this script might seem simple and innocent, let's that a look at what happens when someone provides incorrect or even malicious as in this case, input:
+
Indeed, /timer evaluated the parameter once: $2 is evaluated to 18000 and $3- to "jill's birthday tomorrow!"
 +
 
 +
The associated command of the timer is correctly "notice Mike [Reminder] jill's birthday tomorrow! (Set 18000 seconds ago)", when the timer fires, the /notice command will see its parameters evaluated once, but there is nothing to evaluate in this case.
  
<pre><Mike> !reminder 0 . | ns drop nick | quit hacked! | noop
+
Although this script might seem simple, let's that a look at what happens when someone provides incorrect or even malicious as in this case, input:
 +
 
 +
<pre><Mike> !reminder 0 . | ns drop nick | quit hacked!
 
-Bot- [Reminder] . (Set 0 seconds ago)
 
-Bot- [Reminder] . (Set 0 seconds ago)
 
* Bot (~Bot@isp.example.com) Quit (Quit: hacked!)</pre>
 
* Bot (~Bot@isp.example.com) Quit (Quit: hacked!)</pre>
  
What Mike in our example has performed is known as '''mSL injection attack'''. He provided an input which was interpreted to be part of the code executed by the bot, causing the bot to quit. Let's take a deeper look into what has happened. After the message Mike typed, the bot dropped his nickname and left IRC.
+
This is an msl injection attack. Let's take a deeper look into what has happened:
  
When the timer activated, it will held the following code (you can see that by using /timers or by checking $timer().com):
+
As we know, /timer evaluated the parameter once $2 is evaluated to 0 and $3- is evaluated to ". | ns drop nick | quit hacked!"
  
<syntaxhighlight lang="mirc">notice Mike [Reminder] . | ns drop nick | quit hacked! | noop (Set 0 seconds ago)</syntaxhighlight>
+
So now, the associated command of the timer becomes "notice Mike [Reminder] . | ns drop nick | quit hacked! | noop" and you might be recognizing the pipe character, used to seperate commands, which mIRC will interpret as such, resulting in /ns drop nick and /quit hacked! being executed.
  
Clearly, you can see how the timer command can be extremely dangerous. The unfortunate part is that there is no clean way of solving this problem. The way we recommend to solve this problem is by encoding the input using based64 encoding. Below is an alias to perform this:
+
Clearly, you can see how the timer command can be extremely dangerous. The unfortunate part is that there is no clean way of solving this problem. The only way to prevent this from happening is to encode the problematic parameters so that when they get evaluated, they produce something which needs one more evaluation to produce the correct value. We usually do that by encoding the parameters using based64 encoding. Below is an alias to perform this:
  
 
<syntaxhighlight lang="mirc">alias safe return $!decode( $encode($1-, m) ,m)</syntaxhighlight>
 
<syntaxhighlight lang="mirc">alias safe return $!decode( $encode($1-, m) ,m)</syntaxhighlight>
  
Our new !reminder script looks like this:
+
The spacing is very important. Our new !reminder script now looks like this:
  
 
<syntaxhighlight lang="mirc">on *:text:!reminder & *:#:{
 
<syntaxhighlight lang="mirc">on *:text:!reminder & *:#:{
Line 70: Line 89:
 
     return
 
     return
 
   }
 
   }
   timer 1 $2 notice $nick [Reminder] $safe($3-) (Set $safe($2) seconds ago)
+
   .timer 1 $2 notice $nick [Reminder] $safe($3-) (Set $safe($2) seconds ago)
 
}</syntaxhighlight>
 
}</syntaxhighlight>
 +
 +
Let's take a look at what happens now:
 +
 +
<pre><Mike> !reminder 0 . | ns drop nick | quit hacked!
 +
-Bot- [Reminder] . | ns drop nick | quit hacked! (Set 0 seconds ago)</pre>
 +
 +
And Mike now can't do anything harmful.
 +
 +
/timer will evaluate the parameter as we know but this time, $safe($3-) where $3- is ". | ns drop nick | quit hacked!" is evaluated to "$decode( LiB8IG5zIGRyb3AgbmljayB8IHF1aXQgaGFja2VkIQ== ,m)" and $safe($2) to "$decode( MTgwMDA= ,m)".
 +
 +
The command associated with the timer nows becomes "/notice Mike [Reminder] $decode( LiB8IG5zIGRyb3AgbmljayB8IHF1aXQgaGFja2VkIQ== ,m) (Set $decode( MTgwMDA= ,m) seconds ago)" and those $decode, when evaluated once by /notice, will produce the correct result (the original input of Mike).
 +
 +
Now you don't need to do that for any /timer command of course, only when the parameter is unknown at the time you are writing the script, such as $2 and $3- here.
 +
 +
==== $chan/# ====
 +
 +
You may think $chan can't be evaluated in an malicious way but that's not true, if the $chan (also #) identifier is unknown, you should encode it as well.
 +
 +
The reason for this is that #$identifier will evaluate $identifier, this was originally meant to provide a way to make sure the parameter is a channel name, #$findfile(C:\,*,0) will evaluate, could be pretty annoying.. Watch out for $nick as well, some irc servers allows the '$' character in them.
 +
 +
mIRC allows prefixing an identifier with the pound sign to make mIRC prefix (if needed) the evaluated string the pound symbol. For example:
 +
 +
<syntaxhighlight lang="mirc">//tokenize 32 A #B | echo -a #$1 #$2</syntaxhighlight>
 +
 +
This will produce the following result:
 +
 +
<syntaxhighlight lang="mirc">#A #B</syntaxhighlight>
 +
 +
So what does that have to do with $chan and timers? Everything!
 +
 +
Consider the following bot:
 +
 +
<syntaxhighlight lang="mirc">alias bot_pass return foo_bar_42
 +
on *:connect:{ 
 +
  autojoin -d5 
 +
  ns id $bot_pass
 +
}
 +
on *:invite:#:join #
 +
on !*:join:#:{
 +
  timer 1 2 notice $nick Hello $nick $+ , Welcome to # $+ !
 +
}</syntaxhighlight>
 +
 +
Everything works just fine under normal conditions. What Mike knows better than to testing things under normal conditions:
 +
 +
<pre>* Now talking in #$($bot_pass)
 +
/invite foobar #$($bot_pass)
 +
* Bot joined #$($bot_pass)
 +
/hop #$($bot_pass)
 +
* Attempting to rejoin channel #$($bot_pass)
 +
* Rejoined channel #$($bot_pass)
 +
-Bot- Hello Mike, Welcome to foo_bar_42</pre>
 +
 +
Because $chan (or #) was not escaped, it was evaluated. Mike was able to evaluate the $bot_pass alias and get the bot's password.
 +
  
 
=== /scon and /scid ===
 
=== /scon and /scid ===
Both of these commands were created to evaluate code on a given server. Just like the /timer command, any user input must be escaped somehow (via a variable or encoded).  
+
Both of these commands were created to evaluate/execute code on a given connection. Just like the /timer command, any unknown content must be escaped somehow.
  
Consider this less practical example:
+
Though, unlike /timer, the /scid and /scon are so that when their associated command triggers, they are able to evaluate local variables (but they can't evaluate local identifier like $1-), which makes the escaping easier, you can use the $safe method, but you can simply just use a local variable, consider this less practical example:
  
 
<syntaxhighlight lang="mirc">on *:text:!global *:#:{
 
<syntaxhighlight lang="mirc">on *:text:!global *:#:{
Line 82: Line 155:
 
}</syntaxhighlight>
 
}</syntaxhighlight>
  
The script is designed to do a global amsg (i.e. perform an /amsg on every active connection). Just like the timer command, $1- will be evaluated again in the specified connection which means any user code will become part of your bot's code.
+
The script is designed to do a global amsg (i.e. perform an /amsg on every active connection). Just like the timer command, $1- will be evaluated again in the specified connection when the associated /amsg command triggers, which means any user code will become part of your bot's code.
 +
 
 +
* $safe solution:
 +
 
 +
<syntaxhighlight lang="mirc">on *:text:!global *:#:{
 +
  scon -at1 amsg [AMSG] $safe($1-)
 +
}</syntaxhighlight>
 +
 
 +
* Local variable solution:
 +
 
 +
<syntaxhighlight lang="mirc">on *:text:!global *:#:{
 +
  var %a $1-
 +
  scon -at1 amsg [AMSG] % $+ %a
 +
}</syntaxhighlight>
 +
 
 +
The associated command becomes "amsg [AMSG] %a" and %a is evaluated correctly to produce the user's message.
 +
 
 +
== /flash ==
 +
 
 +
Though /flash is the only command doing it for now, more command might do this in the future. /flash does not take a new command as one of its parameter but it can take a text as an optional parameter, to be used automatically later, that text will be evaluated once by /flash, and mIRC will also evaluate the text parameter once when applying the flash:
 +
 
 +
<syntaxhighlight lang="mirc">on *:text:#:!testflash/flash $ $+ $me</syntaxhighlight>
 +
 
 +
So $ $+ me is evaluate to "$me" by /flash, and $me will be evaluated to your nickname (that text appears in mIRC's titlebar).
  
== Other notable problems ==
+
= Injection with $identifiers =
  
=== $readini() and $read() ===
+
== $readini() and $read() ==
 
If you have visited our help channel, #mSL, and you had some code that used $read() or $readini() without the n switch you would most likely had people yelling at you to always use it. But why? The reason is:
 
If you have visited our help channel, #mSL, and you had some code that used $read() or $readini() without the n switch you would most likely had people yelling at you to always use it. But why? The reason is:
  
Line 140: Line 236:
 
}</syntaxhighlight>
 
}</syntaxhighlight>
  
=== $calc() ===
+
== $calc() ==
 
ALL input to $calc should be validated. Since $calc has its own special evaluation routine, it possesses the ability to evaluate variables from within text. Consider the following code:
 
ALL input to $calc should be validated. Since $calc has its own special evaluation routine, it possesses the ability to evaluate variables from within text. Consider the following code:
  

Revision as of 08:29, 28 August 2014

Template:mIRC Guide

Prerequisite knowledge:
This article is designed to educate people about what mSL injections are, how they happen, and how to prevent them. One should not misuse the information to gain unauthorized access or to execute malicious code, it is provided to make the innocent users aware of the threats they are exposed to when using some of the less understood commands available to mIRC. We will not be held responsible for any damage done.

mSL injection is a code injection technique that exploits mIRC's ability to dynamically evaluate code on the run. With mSLs powerful commands and identifiers, the result of a code injection attack can be disastrous. It is important that you understand what to look for when working with vulnerable commands.

This tutorial will try to summarize for you what commands are problematic, how to detect it, and how to fix it.

An eval injection vulnerability consists of two things:

  • A script that evaluates a code (via a vulnerable command/ident)
  • User input that is not properly validated

It important to understand why this is happening, injections happen because of a simple side effect of the main rules definiting the syntaxes/semantics of the language.

Injection with commands

For a command, the interesting rule used by the parser to make it working is to evaluate $identifiers and %variables once before passing their value to the command:

echo -a $me

Here $me is evaluated, then -a is passed as the first parameter and the value of $me is passed as the second parameter to the /echo command.

Nested command

Now, the language features some commands where one or more of their parameters are actually meant to be a new command with its own parameters. That new command is meant to be used later automatically by mIRC, this is where the injection happens.

Any command like this suffers the problem, we also refer to this problem as the double evaluation problem. They are fortunately not a lot of these commands, here is a list:

  • /timer
  • /scon
  • /scid

The /timer command

Perhaps the most problematic command of them all is the /timer command. The /timer command is used to delay the execution of another command (it has others features but this is the interesting one).

Because of this, you must pass a new command to be executed after the delay to the timer command:

//.timer 1 1 echo -a $day

Which will execute "echo -a $day" one time, after waiting 1 second.

What happens here is that the parameters passed to the /timer command are evaluated once as we saw, so $day gets evaluated once, producing the current day.

The timer will register that its associated command to be executed when the delay has passed is "echo -a <$day's value>"

When the timer fires, its command (a new command) is executed and therefore all of its parameters are, once again, evaluated one time. In this example there is no problem and we can't see the difference because $day can only return a day of the week. If $day is monday, evaluating the plain text monday will always produce monday.

But what if we didn't have $day? what if we had something like $2? Let's consider a more useful example, an innocent reminder script:

on *:text:!reminder & *:#:{
 if ($2 !isnum) {
    notice $nick [Reminder] Syntax Error: !reminder <seconds> <message>
    return
  }
  .timer 1 $2 notice $nick [Reminder] $3- (Set $2 seconds ago)
}

When the correct input is provided, this script works wonderfully. For example:

<Mike> !reminder 18000 jill's birthday tomorrow!
-Bot- [Reminder] jill's birthday tomorrow! (Set 18000 seconds ago)

Indeed, /timer evaluated the parameter once: $2 is evaluated to 18000 and $3- to "jill's birthday tomorrow!"

The associated command of the timer is correctly "notice Mike [Reminder] jill's birthday tomorrow! (Set 18000 seconds ago)", when the timer fires, the /notice command will see its parameters evaluated once, but there is nothing to evaluate in this case.

Although this script might seem simple, let's that a look at what happens when someone provides incorrect or even malicious as in this case, input:

<Mike> !reminder 0 . | ns drop nick | quit hacked!
-Bot- [Reminder] . (Set 0 seconds ago)
* Bot (~Bot@isp.example.com) Quit (Quit: hacked!)

This is an msl injection attack. Let's take a deeper look into what has happened:

As we know, /timer evaluated the parameter once $2 is evaluated to 0 and $3- is evaluated to ". | ns drop nick | quit hacked!"

So now, the associated command of the timer becomes "notice Mike [Reminder] . | ns drop nick | quit hacked! | noop" and you might be recognizing the pipe character, used to seperate commands, which mIRC will interpret as such, resulting in /ns drop nick and /quit hacked! being executed.

Clearly, you can see how the timer command can be extremely dangerous. The unfortunate part is that there is no clean way of solving this problem. The only way to prevent this from happening is to encode the problematic parameters so that when they get evaluated, they produce something which needs one more evaluation to produce the correct value. We usually do that by encoding the parameters using based64 encoding. Below is an alias to perform this:

alias safe return $!decode( $encode($1-, m) ,m)

The spacing is very important. Our new !reminder script now looks like this:

on *:text:!reminder & *:#:{
  if ($2 !isnum) {
    notice $nick [Reminder] Syntax Error: !reminder <seconds> <message>
    return
  }
  .timer 1 $2 notice $nick [Reminder] $safe($3-) (Set $safe($2) seconds ago)
}

Let's take a look at what happens now:

<Mike> !reminder 0 . | ns drop nick | quit hacked!
-Bot- [Reminder] . | ns drop nick | quit hacked! (Set 0 seconds ago)

And Mike now can't do anything harmful.

/timer will evaluate the parameter as we know but this time, $safe($3-) where $3- is ". | ns drop nick | quit hacked!" is evaluated to "$decode( LiB8IG5zIGRyb3AgbmljayB8IHF1aXQgaGFja2VkIQ== ,m)" and $safe($2) to "$decode( MTgwMDA= ,m)".

The command associated with the timer nows becomes "/notice Mike [Reminder] $decode( LiB8IG5zIGRyb3AgbmljayB8IHF1aXQgaGFja2VkIQ== ,m) (Set $decode( MTgwMDA= ,m) seconds ago)" and those $decode, when evaluated once by /notice, will produce the correct result (the original input of Mike).

Now you don't need to do that for any /timer command of course, only when the parameter is unknown at the time you are writing the script, such as $2 and $3- here.

$chan/#

You may think $chan can't be evaluated in an malicious way but that's not true, if the $chan (also #) identifier is unknown, you should encode it as well.

The reason for this is that #$identifier will evaluate $identifier, this was originally meant to provide a way to make sure the parameter is a channel name, #$findfile(C:\,*,0) will evaluate, could be pretty annoying.. Watch out for $nick as well, some irc servers allows the '$' character in them.

mIRC allows prefixing an identifier with the pound sign to make mIRC prefix (if needed) the evaluated string the pound symbol. For example:

//tokenize 32 A #B | echo -a #$1 #$2

This will produce the following result:

#A #B

So what does that have to do with $chan and timers? Everything!

Consider the following bot:

alias bot_pass return foo_bar_42
on *:connect:{  
  autojoin -d5  
  ns id $bot_pass
}
on *:invite:#:join #
on !*:join:#:{
  timer 1 2 notice $nick Hello $nick $+ , Welcome to # $+ !
}

Everything works just fine under normal conditions. What Mike knows better than to testing things under normal conditions:

* Now talking in #$($bot_pass)
/invite foobar #$($bot_pass)
* Bot joined #$($bot_pass)
/hop #$($bot_pass)
* Attempting to rejoin channel #$($bot_pass)
* Rejoined channel #$($bot_pass)
-Bot- Hello Mike, Welcome to foo_bar_42

Because $chan (or #) was not escaped, it was evaluated. Mike was able to evaluate the $bot_pass alias and get the bot's password.


/scon and /scid

Both of these commands were created to evaluate/execute code on a given connection. Just like the /timer command, any unknown content must be escaped somehow.

Though, unlike /timer, the /scid and /scon are so that when their associated command triggers, they are able to evaluate local variables (but they can't evaluate local identifier like $1-), which makes the escaping easier, you can use the $safe method, but you can simply just use a local variable, consider this less practical example:

on *:text:!global *:#:{
  scon -at1 amsg [AMSG] $1-
}

The script is designed to do a global amsg (i.e. perform an /amsg on every active connection). Just like the timer command, $1- will be evaluated again in the specified connection when the associated /amsg command triggers, which means any user code will become part of your bot's code.

  • $safe solution:
on *:text:!global *:#:{
  scon -at1 amsg [AMSG] $safe($1-)
}
  • Local variable solution:
on *:text:!global *:#:{
  var %a $1-
  scon -at1 amsg [AMSG] % $+ %a
}

The associated command becomes "amsg [AMSG] %a" and %a is evaluated correctly to produce the user's message.

/flash

Though /flash is the only command doing it for now, more command might do this in the future. /flash does not take a new command as one of its parameter but it can take a text as an optional parameter, to be used automatically later, that text will be evaluated once by /flash, and mIRC will also evaluate the text parameter once when applying the flash:

on *:text:#:!testflash/flash $ $+ $me

So $ $+ me is evaluate to "$me" by /flash, and $me will be evaluated to your nickname (that text appears in mIRC's titlebar).

Injection with $identifiers

$readini() and $read()

If you have visited our help channel, #mSL, and you had some code that used $read() or $readini() without the n switch you would most likely had people yelling at you to always use it. But why? The reason is:

By default BOTH $read() and $readini() treat the text in the file as code!

So what are some of its consequences? Consider the following greet script:

on *:text:!setgreet*:#:{
  if (!$2-) { notice $nick Syntax Error: !setgreet <greet> | return }
  writeini greets.ini greet $nick $2-
  notice $nick [Greet] Greet Saved.
}
on !*:join:#:{
  if ($readini(greets.ini, greet, $nick)) {
    msg $chan [[ $+ $nick $+ ] $v1
  }
}

Let's see what happens:

<Mike> !setgreet
-Bot- Syntax Error: !setgreet <greet>
<Mike> !setgreet Two things are certain in life, death and taxes. - Benjamin Franklin
-Bot- [Greet] Greet Saved.
/hop
* Rejoined channel #foobar
<Bot> [foobar] Two things are certain in life, death and taxes. - Benjamin Franklin
<Mike> !setgreet Hello $findfile(C:\, *, 1, quit hacked!)
-Bot- [Greet] Greet Saved.
/hop
* Rejoined channel #foobar
* Bot (~Bot@isp.example.com) Quit (Quit: hacked!)

In this example Mike used the fact that $findfile() can execute a command when it finds a matching file. It should be clear how dangerous this can be.

So how do we fix it? Using the n option!

$read(... , n, ...)
;and 
$readini(..., n, ...)

Our new code would look like this:

on *:text:!setgreet*:#:{
  if (!$2-) { notice $nick Syntax Error: !setgreet <greet> | return }
  writeini greets.ini greet $nick $2-
  notice $nick [Greet] Greet Saved.
}
on !*:join:#:{
  if ($readini(greets.ini, n, greet, $nick)) {
    msg $chan [[ $+ $nick $+ ] $v1
  }
}

$calc()

ALL input to $calc should be validated. Since $calc has its own special evaluation routine, it possesses the ability to evaluate variables from within text. Consider the following code:

//var %x = 12345 | tokenize 32 % $+ x | echo -a $1 = $calc($1)

Which will produce the following result:

%x = 12345

Surprise! We bet you did not expect that to happen. (This is actually a side effect of $calc being able to evaluate variables inside parenthesis or immediately after an operator without a space.)

Now let's take a look at a more practical example:

; Assume %password is set to 123456
on *:connect:{
  autojoin -d5
  if ($network == UnderNet) {
    msg x@channels.undernet.org login Wiz126 %password
  }
}
on *:text:!calc *:#:{
  msg $nick [Calc] $2- = $calc($2-)
}

Like most people, this user has an on connect event to register his user and a simple !calc script.

Let's see what Mike can do to him:

<Mike> !calc 3*5+55
<Wiz126> [Calc] 3*5+55 = 70
<Mike> !calc %password
<Wiz126> [Calc] %password = 123456

It's clear how Mike was able to get the value of a pretty important variable.

It's important to note that EVEN if %password was set to "1234rosebud". The $calc() will return "1234", which is not the whole password but it's a big chunk of it.

$chan and sometimes $nick

Just because $chan doesn't come directly from the user's text doesn't make it any less dangerous. Use $safe($chan) EVERYWHERE!

mIRC allows prefixing an identifier with the pound sign to make mIRC prefix (if needed) the evaluated string the pound symbol. For example:

//tokenize 32 A #B | echo -a #$1 #$2

This will produce the following result:

#A #B

So what does that have to do with $chan and timers? Everything!

Consider the following bot:

alias bot_pass return foo_bar_42
on *:connect:{  
  autojoin -d5  
  ns id $bot_pass
}
on *:invite:#:join #
on !*:join:#:{
  timer 1 2 notice $nick Hello $nick $+ , Welcome to # $+ !
}

Everything works just fine under normal conditions. What Mike knows better than to testing things under normal conditions:

* Now talking in #$($bot_pass)
/invite foobar #$($bot_pass)
* Bot joined #$($bot_pass)
/hop #$($bot_pass)
* Attempting to rejoin channel #$($bot_pass)
* Rejoined channel #$($bot_pass)
-Bot- Hello Mike, Welcome to foo_bar_42

Because $chan (or #) was not escaped, it was evaluated. Mike was able to evaluate the $bot_pass alias and get the bot's password.

So how do we fix it? Simple! Use the $safe alias to encode the channel!

; ...
alias safe return $!decode( $encode($1-, m) ,m)
on !*:join:#:{
  timer 1 2 notice $nick Hello $nick $+ , Welcome to $safe(#) $+ !
}

Note: On some really obscure networks, $nick is actually allowed to have $. Watch out!