Post-ID in url differs from $post->ID

There are two ways to get what you need. One is mentioned in Identify the page being shown while in The Loop:

$post_id = get_queried_object_id();

Unfortunately, this might break. get_queried_object_id() looks in the global variable $wp_query for the ID, and that variable can be overwritten during the page rendering by query_posts(). The same is true for functions like is_page().

But you can fetch the real post ID earlier, during template_redirect, store its value, and get it in your widget class.

First we need a helper function that collects the post ID for later usage:

add_action( 'template_redirect', 'collect_real_post_id' );

function collect_real_post_id()
{
    static $post_id = 0;

    if ( is_singular() && 'wp_head' === current_filter() )
        $post_id = get_queried_object_id();

    return $post_id;
}

And now you can use that value wherever you want, for example in your widget() method:

class My_Widget extends WP_Widget
{

    public function widget( $args, $instance )
    {
        $post_id = collect_real_post_id();

        if ( ! $post_id ) // 0 evaluates to FALSE
            return;

        // do something with the post ID
    }
}