Showing image description along with embedded links below post images

When you look at the last line img_caption_shortcode(), then you can see wp_kses() stripping non allowed HTML tags there. The wp_kses_hook() function does only wrap a filter:

return apply_filters( 'pre_kses', $string, $allowed_html, $allowed_protocols );

which allows to hook in and alter the output before wp_kses_split() finally removes the HTML tag by using preg_replace_callback with _wp_kses_split_callback() as function to remove the tags.

As inside _wp_kses_split_callback(), the wp_kses_split2() function is called, you got a (bad) alternative as well: Altering a global.

You got the following two globals in the callback:

global $pass_allowed_html, $pass_allowed_protocols;

Meaning that you can do the following as well (not recommended):

// Fetch the global context
global $pass_allowed_html;
// Save the global context for restoring it to not nuke other code
$temp = $pass_allowed_html;
// Alter the context
$GLOBALS['pass_allowed_html'] = [ /* your allowed HTML tags here */ ];
// Fetch the result
$result = img_caption_shortcode();
// Reset the global context
$GLOBALS['pass_allowed_html'] = $temp;
// Return your altered result
return $result;

Keep in mind that wp_kses_allowed_html() will then trigger it’s explicit case.

As img_caption_shortcode() only passes the string of value post to wp_kses(), you can as well then take profit from the following lines inside wp_kses_split2():

if ( ! is_array( $allowed_html ) )
    $allowed_html = wp_kses_allowed_html( $allowed_html );

Looking at [wp_kses_allowed_html()] again:

return apply_filters( 'wp_kses_allowed_html', $allowedposttags, $context );

you can simply use this filter. Make sure that you remove the filter again after it has run to not conflict with other content and therefore allowing the tag elsewhere by accident.