- Samuel Chukwueze (37')
- Marco Reus (10' Pen)
- Jamie Bynoe-Gittens (59')
- Karim Adeyemi (69')
Milan, bottom on five points from five games, can still secure a top-two finish if they win at Newcastle and Dortmund beat PSG in the final round of games next month.
Milan could have taken an early lead when they were awarded a penalty for a handball but Dortmund keeper Gregor Kobel pulled off a superb save to deny Olivier Giroud from the spot in the sixth minute.
The tables turned four minutes later when Dortmund midfielder Reus put the visitors in front with a penalty kick after the referee pointed to the spot following Davide Calabria's foul on Bynoe-Gittens.
Samuel Chukwueze levelled for Milan from inside the box eight minutes before halftime, scoring his first goal for the seven-time European champions since he joined from LaLiga side Villarreal in July.
Chukwueze filled in admirably for the absent Rafael Leao in the opening 45 minutes, after the Portuguese suffered a thigh injury in a Serie A match two weeks ago.
However, the hosts lost their spark after the break and the 19-year-old Bynoe-Gittens restored the lead for Dortmund just before the hour mark with a low shot from the edge of the box from an excellent Marcel Sabitzer pass.
Substitute Adeyemi made it 3-1 in the 69th minute after Milan keeper Mike Maignan twice tried to claw his powerful shot away but the ball made it past the goal line anyway.
Dortmund forward Niclas Fullkrug came close to increasing the scoreline two minutes from time but his shot crashed against the bar, three minutes after Milan's Luka Jovic had hit the post with a header.
Dortmund began their Champions League season with a 2-0 loss in Paris before being held to a goalless draw at home by Milan, but breathed life into their campaign by beating Newcastle both away (1-0) and at home (2-0).
They will be confirmed as group winners if they avoid a home defeat by PSG.
- Istvan Kovacs